[
  {
    "path": ".github/workflows/github_actions_build.yml",
    "content": "# Shameless copy of @AlastairUK's fine work for the jpl_eph repo.\n# Aside from the comment,  this is almost a byte-for-byte copy.\nname: Build\n\non: [push, pull_request]\n\njobs:\n  buildUbuntu:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@master\n    - name: make\n      run: |\n        git clone https://github.com/Bill-Gray/lunar\n        cd lunar\n        make ERRORS=Y liblunar.a\n        make install\n        cd ..\n        make ERRORS=Y\n\n  buildOSX:\n    runs-on: macOS-latest\n    steps:\n    - uses: actions/checkout@master\n    - name: make\n      run: |\n        git clone https://github.com/Bill-Gray/lunar\n        cd lunar\n        make CC=clang ERRORS=Y liblunar.a\n        make install\n        cd ..\n        make CC=clang ERRORS=Y\n\n  buildWindows:\n    runs-on: windows-latest\n    steps:\n    - uses: actions/checkout@master\n    - name: make\n      run: |\n        call \"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Auxiliary\\Build\\vcvarsall.bat\" x64\n        git clone https://github.com/Bill-Gray/lunar\n        mkdir myincl\n        cd lunar\n        nmake -f lunar.mak lunar64.lib\n        nmake -f lunar.mak install\n        cd ..\n        set CL=/I\"./myincl\"\n        copy lunar\\*.lib .\n        nmake -f msvc.mak\n      shell: cmd\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n*.cgi\nget_high\nmergetle\nobs_test\nobs_tes2\nout_comp\nsat_id\nsat_id2\ntest2\ntest_out\ntest_sat\ntle_date\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020,  Project Pluto\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# sat_code\n\nC/C++ code for the SGP4/SDP4 satellite motion model,  and for manipulating TLEs\n(Two-Line Elements). Full details at\n[http://www.projectpluto.com/sat_code.htm](http://www.projectpluto.com/sat_code.htm).\n\nThe only dependency is on the [`lunar`](https://github.com/Bill-Gray/lunar) (basic\nastronomical ephemeris/time functions) library.  Make and `make install` that\nlibrary before attempting to build this code.\n\nOn Linux,  run `make` to build the library and various test executables.\n(You can also do this with MinGW under Windows.)  In Linux,  you\ncan then run `make install` to put libraries in `/usr/local/lib` and some\ninclude files in `/usr/local/include`.  (You will probably have to make that\n`sudo make install`.)  For BSD,  and probably OS/X,  run `gmake CLANG=Y`\n(GNU make,  with the clang compiler),  then `sudo gmake install`.\n\nOn Windows,  run `nmake -f msvc.mak` with MSVC++.  Optionally,  add\n`-BITS_32=Y` for 32-bit code.\n"
  },
  {
    "path": "basics.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n/*------------------------------------------------------------------*/\n\n/* FMOD2P */\ndouble FMod2p( const double x)\n{\n   double rval = fmod( x, twopi);\n\n   if( rval < 0.)\n      rval += twopi;\n   return( rval);\n} /* fmod2p */\n\n#define EPHEM_TYPE_DEFAULT    '0'\n#define EPHEM_TYPE_SGP        '1'\n#define EPHEM_TYPE_SGP4       '2'\n#define EPHEM_TYPE_SDP4       '3'\n#define EPHEM_TYPE_SGP8       '4'\n#define EPHEM_TYPE_SDP8       '5'\n#define EPHEM_TYPE_HIGH       'H'\n\n/*------------------------------------------------------------------*/\n\nvoid sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg);\n                                                            /* common.c */\n\n/* Selects the type of ephemeris to be used (SGP*-SDP*) */\nint DLL_FUNC select_ephemeris( const tle_t *tle)\n{\n   int rval;\n\n   if( tle->ephemeris_type == EPHEM_TYPE_HIGH)\n      rval = 1;         /* force high-orbit state vector model */\n   else if( tle->xno <= 0. || tle->eo > 1. || tle->eo < 0.)\n      rval = -1;                 /* error in input data */\n   else if( tle->ephemeris_type == EPHEM_TYPE_SGP4\n         || tle->ephemeris_type == EPHEM_TYPE_SGP8)\n      rval = 0;         /* specifically marked non-deep */\n   else if( tle->ephemeris_type == EPHEM_TYPE_SDP4\n         || tle->ephemeris_type == EPHEM_TYPE_SDP8)\n      rval = 1;         /* specifically marked deep */\n   else\n      {\n      deep_arg_t deep_arg;\n\n      sxpall_common_init( tle, &deep_arg);\n      /* Select a deep-space/near-earth ephemeris */\n      /* If the orbital period is greater than 225 minutes... */\n      if (twopi / deep_arg.xnodp >= 225.)\n         rval = 1;      /* yes,  it should be a deep-space (SDPx) ephemeris */\n      else\n         rval = 0;      /* no,  you can go with an SGPx ephemeris */\n      }\n   return( rval);\n} /* End of select_ephemeris() */\n\n/*------------------------------------------------------------------*/\n\nlong DLL_FUNC sxpx_library_version( void)\n{\n   return( 0x100);\n}\n"
  },
  {
    "path": "common.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE. */\n\n#include <math.h>\n#include <assert.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n/* params[1] and [6]-[9] were used in earlier implementations,  but are\n   now unused */\n\n#define c2           params[0]\n#define c1           params[2]\n#define c4           params[3]\n#define xnodcf       params[4]\n#define t2cof        params[5]\n\nvoid sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg)\n{\n   const double a1 = pow(xke / tle->xno, two_thirds);  /* in Earth radii */\n   double del1, ao, delo, tval;\n\n   /* Recover original mean motion (xnodp) and   */\n   /* semimajor axis (aodp) from input elements. */\n   deep_arg->cosio = cos( tle->xincl);\n   deep_arg->cosio2 = deep_arg->cosio * deep_arg->cosio;\n   deep_arg->eosq = tle->eo*tle->eo;\n   deep_arg->betao2 = 1-deep_arg->eosq;\n   deep_arg->betao = sqrt(deep_arg->betao2);\n   tval = 1.5 * ck2 * (3. * deep_arg->cosio2 - 1.) / (deep_arg->betao * deep_arg->betao2);\n   del1 = tval / (a1 * a1);\n   ao = a1 * (1. - del1 * (1. / 3. + del1 * ( 1. + 134./81. * del1)));\n   delo = tval / (ao * ao);\n   deep_arg->xnodp = tle->xno / (1+delo);   /* in radians/minute */\n   deep_arg->aodp = ao / (1-delo);\n}\n\nvoid sxpx_common_init( double *params, const tle_t *tle,\n                                  init_t *init, deep_arg_t *deep_arg)\n{\n   double\n         eeta, etasq, perige, pinv, pinvsq,\n         psisq, qoms24, temp1, temp2, temp3,\n         cosio4, tsi_squared, x3thm1, xhdot1;\n\n   sxpall_common_init( tle, deep_arg);\n   x3thm1 = 3. * deep_arg->cosio2 - 1.;\n   /* For perigee below 156 km, the values */\n   /* of s and qoms2t are altered.         */\n   init->s4 = s_const;\n   qoms24 = qoms2t;\n   perige = (deep_arg->aodp * (1-tle->eo) - ae) * earth_radius_in_km;\n   if( perige < 156.)\n      {\n      double temp_val, temp_val_squared;\n\n      if(perige <= 98.)\n         init->s4 = 20;\n      else\n         init->s4 = perige-78.;\n      temp_val = (120. - init->s4) * ae / earth_radius_in_km;\n      temp_val_squared = temp_val * temp_val;\n      qoms24 = temp_val_squared * temp_val_squared;\n      init->s4 = init->s4 / earth_radius_in_km + ae;\n      }  /* End of if(perige <= 156) */\n\n   pinv = 1. / (deep_arg->aodp * deep_arg->betao2);\n   pinvsq = pinv * pinv;\n   init->tsi = 1. / (deep_arg->aodp - init->s4);\n   init->eta = deep_arg->aodp*tle->eo*init->tsi;\n   etasq = init->eta*init->eta;\n   eeta = tle->eo*init->eta;\n   psisq = fabs(1-etasq);\n   tsi_squared = init->tsi * init->tsi;\n   init->coef = qoms24 * tsi_squared * tsi_squared;\n   init->coef1 = init->coef / pow(psisq,3.5);\n   c2 = init->coef1 * deep_arg->xnodp * (deep_arg->aodp*(1+1.5*etasq+eeta*\n   (4+etasq))+0.75*ck2*init->tsi/psisq*x3thm1*(8+3*etasq*(8+etasq)));\n   c1 = tle->bstar*c2;\n   deep_arg->sinio = sin(tle->xincl);\n   c4 = 2*deep_arg->xnodp*init->coef1*deep_arg->aodp*deep_arg->betao2*\n        (init->eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*init->tsi/\n        (deep_arg->aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq*\n        (1.5-0.5*eeta))+0.75*(1. - deep_arg->cosio2) *(2*etasq-eeta*(1+etasq))*\n        cos(2*tle->omegao)));\n   cosio4 = deep_arg->cosio2 * deep_arg->cosio2;\n   temp1 = 3*ck2*pinvsq*deep_arg->xnodp;\n   temp2 = temp1 * ck2 * pinvsq;\n   temp3 = 1.25 * ck4 * pinvsq * pinvsq * deep_arg->xnodp;\n   deep_arg->xmdot = deep_arg->xnodp\n            + temp1 * deep_arg->betao* x3thm1 / 2.\n            + temp2 * deep_arg->betao*\n                    (13-78*deep_arg->cosio2+137*cosio4) / 16.;\n   deep_arg->omgdot = -temp1 * (1. - 5 * deep_arg->cosio2) / 2.\n              + temp2 * (7-114*deep_arg->cosio2+395*cosio4) / 16.\n              + temp3 * (3-36*deep_arg->cosio2+49*cosio4);\n   xhdot1 = -temp1*deep_arg->cosio;\n   deep_arg->xnodot = xhdot1+(temp2*(4-19*deep_arg->cosio2) / 2.\n           + 2*temp3*(3-7*deep_arg->cosio2))*deep_arg->cosio;\n   xnodcf = 3.5*deep_arg->betao2*xhdot1*c1;\n   t2cof = 1.5*c1;\n}\n\ninline double centralize_angle( const double ival)\n{\n   double rval = fmod( ival, twopi);\n\n   if( rval > pi)\n      rval -= twopi;\n   else if( rval < - pi)\n      rval += twopi;\n   return( rval);\n}\n\n#define MAX_KEPLER_ITER 10\n\nint sxpx_posn_vel( const double xnode, const double a, const double ecc,\n      const double cosio, const double sinio,\n      const double xincl, const double omega,\n      const double xl, double *pos, double *vel)\n{\n  /* Long period periodics */\n   const double axn = ecc*cos(omega);\n   double temp = 1/(a*(1.-ecc*ecc));\n   const double xlcof = .125 * a3ovk2 * sinio * (3+5*cosio)/ (1. + cosio);\n   const double aycof = 0.25 * a3ovk2 * sinio;\n   const double xll = temp*xlcof*axn;\n   const double aynl = temp*aycof;\n   const double xlt = xl+xll;\n   const double ayn = ecc*sin(omega)+aynl;\n   const double elsq = axn*axn+ayn*ayn;\n   const double capu = centralize_angle( xlt - xnode);\n   const double chicken_factor_on_eccentricity = 1.e-6;\n   double epw = capu;\n   double temp1, temp2;\n   double ecosE, esinE, pl, r;\n   double betal;\n   double u, sinu, cosu, sin2u, cos2u;\n   double rk, uk, xnodek, xinck;\n   double sinuk, cosuk, sinik, cosik, sinnok, cosnok, xmx, xmy;\n   double sinEPW, cosEPW;\n   double ux, uy, uz;\n   int i, rval = 0;\n\n/* Dundee changes:  items dependent on cosio get recomputed: */\n   const double cosio_squared = cosio * cosio;\n   const double x3thm1 = 3.0 * cosio_squared - 1.0;\n   const double sinio2 = 1.0 - cosio_squared;\n   const double x7thm1 = 7.0 * cosio_squared - 1.0;\n\n                /* Added 29 Mar 2003,  modified 26 Sep 2006:  extremely    */\n                /* decayed satellites can end up \"orbiting\" within the     */\n                /* earth.  Eventually,  the semimajor axis becomes zero,   */\n                /* then negative.  In that case,  or if the orbit is near  */\n                /* to parabolic,  we zero the posn/vel and quit.  If the   */\n                /* object has a perigee or apogee indicating a crash,  we  */\n                /* just flag it.  Revised 28 Oct 2006.                     */\n\n   if( a < 0.)\n      rval = SXPX_ERR_NEGATIVE_MAJOR_AXIS;\n   if( elsq > 1. - chicken_factor_on_eccentricity)\n      rval = SXPX_ERR_NEARLY_PARABOLIC;\n   for( i = 0; i < 3; i++)\n      {\n      pos[i] = 0.;\n      if( vel)\n         vel[i] = 0.;\n      }\n   if( rval)\n      return( rval);\n   if( a * (1. - ecc) < 1. && a * (1. + ecc) < 1.)   /* entirely within earth */\n      rval = SXPX_WARN_ORBIT_WITHIN_EARTH;     /* remember, e can be negative */\n   if( a * (1. - ecc) < 1. || a * (1. + ecc) < 1.)   /* perigee within earth */\n      rval = SXPX_WARN_PERIGEE_WITHIN_EARTH;\n  /* Solve Kepler's' Equation */\n   for( i = 0; i < MAX_KEPLER_ITER; i++)\n      {\n      const double newton_raphson_epsilon = 1e-12;\n      double f, fdot, delta_epw;\n      int do_second_order_newton_raphson = 1;\n\n      sinEPW = sin( epw);\n      cosEPW = cos( epw);\n      ecosE = axn * cosEPW + ayn * sinEPW;\n      esinE = axn * sinEPW - ayn * cosEPW;\n      f = capu - epw + esinE;\n      if (fabs(f) < newton_raphson_epsilon) break;\n      fdot = 1. - ecosE;\n      delta_epw = f / fdot;\n      if( !i)\n         {\n         const double max_newton_raphson = 1.25 * fabs( ecc);\n\n         do_second_order_newton_raphson = 0;\n         if( delta_epw > max_newton_raphson)\n            delta_epw = max_newton_raphson;\n         else if( delta_epw < -max_newton_raphson)\n            delta_epw = -max_newton_raphson;\n         else\n            do_second_order_newton_raphson = 1;\n         }\n      if( do_second_order_newton_raphson)\n         delta_epw = f / (fdot + 0.5*esinE*delta_epw);\n                             /* f/(fdot - 0.5*fdotdot * f / fdot) */\n      epw += delta_epw;\n      }\n\n   if( i == MAX_KEPLER_ITER)\n      return( SXPX_ERR_CONVERGENCE_FAIL);\n\n  /* Short period preliminary quantities */\n   temp = 1-elsq;\n   pl = a*temp;\n   r = a*(1-ecosE);\n   temp2 = a / r;\n   betal = sqrt(temp);\n   temp = esinE/(1+betal);\n   cosu = temp2 * (cosEPW - axn + ayn * temp);\n   sinu = temp2 * (sinEPW - ayn - axn * temp);\n   u = atan2( sinu, cosu);\n   sin2u = 2*sinu*cosu;\n   cos2u = 2*cosu*cosu-1;\n   temp1 = ck2 / pl;\n   temp2 = temp1 / pl;\n\n  /* Update for short periodics */\n   rk = r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*sinio2*cos2u;\n   uk = u-0.25*temp2*x7thm1*sin2u;\n   xnodek = xnode+1.5*temp2*cosio*sin2u;\n   xinck = xincl+1.5*temp2*cosio*sinio*cos2u;\n\n  /* Orientation vectors */\n   sinuk = sin(uk);\n   cosuk = cos(uk);\n   sinik = sin(xinck);\n   cosik = cos(xinck);\n   sinnok = sin(xnodek);\n   cosnok = cos(xnodek);\n   xmx = -sinnok*cosik;\n   xmy = cosnok*cosik;\n   ux = xmx*sinuk+cosnok*cosuk;\n   uy = xmy*sinuk+sinnok*cosuk;\n   uz = sinik*sinuk;\n\n  /* Position and velocity */\n   pos[0] = rk * ux * earth_radius_in_km;\n   pos[1] = rk * uy * earth_radius_in_km;\n   pos[2] = rk * uz * earth_radius_in_km;\n   if( vel)\n      {\n      const double rdot = xke * sqrt(a) * esinE / r;\n      const double rfdot = xke * sqrt(pl) / r;\n      const double xn = xke / (a * sqrt(a));\n      const double rdotk = rdot - xn * temp1 * sinio2 * sin2u;\n      const double rfdotk = rfdot + xn * temp1 * (sinio2 * cos2u + 1.5 * x3thm1);\n      const double vx = xmx * cosuk - cosnok * sinuk;\n      const double vy = xmy * cosuk - sinnok * sinuk;\n      const double vz = sinik*cosuk;\n\n      vel[0] = (rdotk * ux + rfdotk * vx) * earth_radius_in_km;\n      vel[1] = (rdotk * uy + rfdotk * vy) * earth_radius_in_km;\n      vel[2] = (rdotk * uz + rfdotk * vz) * earth_radius_in_km;\n      }\n   return( rval);\n} /*SGP4*/\n"
  },
  {
    "path": "deep.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE. */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n      /* omega_E = number of (sidereal) rotations of the earth per UT day: */\nconst double omega_E = 1.00273790934;\n#ifdef USE_ACCURATE_ANOMALISTICS\n         /* The anomalistic month is the mean time it takes the moon to go\n            from perigee to perigee.  The anomalistic year is the mean time\n            it takes the earth to go from perihelion to perihelion.\n            The following lines compute the \"correct\" mean motions of\n            the earth and sun: zns_per_day is the rate of change of\n            the earth's mean anomaly,  in radians per day,  and the 'znl'\n            quantities give similar rates for the moon.\n               Problem is,  the original SxPx sources give values that are\n            close to,  but not exactly equal to,  these values.  The\n            \"new\" values are probably improvements from further observations,\n            but if you actually used them,  you'd break compatibility with\n            older implementations,  and wouldn't match up with the way\n            NORAD and others actually compute TLEs.  So the following few\n            lines should be regarded as explanatory;  we're stuck with using\n            the older,  less accurate SxPx values.  */\nconst double days_per_anomalistic_month =  27.554551;\nconst double days_per_anomalistic_year = 365.259635864;\nconst double zns_per_day = twopi / days_per_anomalistic_year;\nconst double zns_per_min = zns_per_day / minutes_per_day;\nconst double znl_per_day = twopi / days_per_anomalistic_month;\nconst double znl_per_min = znl_per_day / minutes_per_day;\n         /* thdt = angular velocity of the earth,  in radians/minute. */\n         /* Again,  we have to use a less accurate value from the original */\n         /* SxPx,  to replicate everybody else's results.                  */\nconst double thdt = twopi * omega_E / minutes_per_day;\n#else\nconst double zns_per_min = 1.19459E-5;\nconst double zns_per_day = 0.017201977;\nconst double znl_per_day = 0.228027132;\nconst double znl_per_min = 1.5835218E-4;\nconst double thdt =   4.37526908801129966e-3;\n#endif\n         /* zes = mean eccentricity of earth's orbit */\n         /* zel = mean eccentricity of the moon's orbit */\n#define zes      0.01675\n#define zel      0.05490\n\n/* thetag: computes Greenwich sidereal time,  as an angle in radians\nfrom 0 to 2*pi,  for a given UT0 JD. */\n\nstatic inline double ThetaG( const double jd)\n{\n                 /* Reference:  The 1992 Astronomical Almanac, page B6. */\n                 /* Earth rotations per sidereal day (non-constant) */\n  const double UT = fmod( jd + .5, 1.);\n  const double seconds_per_day = 86400.;\n  const double jd_2000 = 2451545.0;   /* 1.5 Jan 2000 = JD 2451545. */\n  double t_cen, GMST, rval;\n\n  t_cen = (jd - UT - jd_2000) / 36525.;\n  GMST = 24110.54841 + t_cen * (8640184.812866 + t_cen *\n                           (0.093104 - t_cen * 6.2E-6));\n  GMST = fmod( GMST / seconds_per_day + omega_E * UT, 1.);\n  if( GMST < 0.)\n     GMST += 1.;\n  rval = twopi * GMST;\n\n  return( rval);\n} /*Function thetag*/\n\n      /* Previously,  the integration step was given as two variables:      */\n      /* 'stepp' (positive step = +720) and 'stepn' (negative step = -720). */\n      /* Exactly why this should be made a variable,  much less _different_ */\n      /* variables for positive and negative,  is entirely unclear...       */\n      /* (8 Apr 2003) INTEGRATION_STEP is now a maximum integration step.   */\n      /* The code in 'dpsec' splits the integration range into equally-sized */\n      /* pieces of 720 minutes (half a day) or smaller.                      */\n      /* (25 Aug 2006) INTEGRATION_STEP is now the variable                  */\n      /* 'dpsec_integration_step' so I can experiment with different         */\n      /* integration techniques & evaluate their errors.                     */\n\nstatic double dpsec_integration_step = 720.;\nstatic int dpsec_integration_order = 2;\nstatic int is_dundee_compliant = 0;\n\nvoid DLL_FUNC sxpx_set_implementation_param( const int param_index,\n                                              const int new_param)\n{\n   switch( param_index)\n      {\n      case SXPX_DPSEC_INTEGRATION_ORDER:\n         dpsec_integration_order = new_param;\n         break;\n      case SXPX_DUNDEE_COMPLIANCE:\n         is_dundee_compliant = new_param;\n         break;\n      }\n}\n\nvoid DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size)\n{\n   dpsec_integration_step = new_step_size;\n}\n\nstatic inline double eval_cubic_poly( const double x, const double constant,\n               const double linear, const double quadratic_term,\n               const double cubic_term)\n{\n   return( constant + x * (linear + x * (quadratic_term + x * cubic_term)));\n}\n\n/* DEEP */\nvoid Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg)\n{\n   const double sinq = sin(tle->xnodeo);\n   const double cosq = cos(tle->xnodeo);\n   const double aqnv = 1/deep_arg->aodp;\n   const double c1ss   =  2.9864797E-6;\n           /* 1900 Jan 0.5 = JD 2415020. */\n   const double days_since_1900 = tle->epoch - 2415020.;\n           /* zcosi, zsini start as cos & sin of obliquity of earth's  */\n           /* orbit = 23.444100 degrees... matches obliquity in 1963; */\n           /* probably just a slightly inaccurate value: */\n   const double zcosi0 = 0.91744867;\n   const double zsini0 = 0.39785416;\n   double zcosi = zcosi0;\n   double zsini = zsini0;\n           /* zcosg, zsing start as cos & sin of -78.779197 degrees */\n   double zsing = -0.98088458;\n   double zcosg =  0.1945905;\n   double bfact, cc = c1ss, se;\n   double ze = zes, zn = zns_per_min;\n   double sgh, sh, si;\n   double zsinh = sinq, zcosh = cosq;\n   double zcosil, zsinil, zcoshl, zsinhl;\n   double zcosgl, zsingl;\n   double sl;\n   int iteration;\n\n   deep_arg->thgr = ThetaG( tle->epoch);\n   deep_arg->xnq = deep_arg->xnodp;\n   deep_arg->omegaq = tle->omegao;\n\n/* if( days_since_1900 != deep_arg->preep)   */\n      {\n      const double lunar_asc_node = 4.5236020 - 9.2422029E-4 * days_since_1900;\n      const double sin_asc_node = sin(lunar_asc_node);\n      const double cos_asc_node = cos(lunar_asc_node);\n      const double c_minus_gam = znl_per_day * days_since_1900 - 1.1151842;\n            /* gam = longitude of perigee for the moon,  in radians: */\n      const double gam = 5.8351514 + 0.0019443680 * days_since_1900;\n      double zx, zy;\n\n      deep_arg->preep = days_since_1900;\n      zcosil = 0.91375164 - 0.03568096 * cos_asc_node;\n      zsinil = sqrt(1. - zcosil * zcosil);\n      zsinhl = 0.089683511 * sin_asc_node / zsinil;\n      zcoshl = sqrt(1. - zsinhl*zsinhl);\n      deep_arg->zmol = FMod2p( c_minus_gam);\n      zx = zsini0 * sin_asc_node / zsinil;\n      zy = zcoshl * cos_asc_node + zcosi0 * zsinhl * sin_asc_node;\n      zx = atan2( zx, zy) + gam - lunar_asc_node;\n      zcosgl = cos( zx);\n      zsingl = sin( zx);\n      deep_arg->zmos = FMod2p( 6.2565837\n                     + zns_per_day * days_since_1900);\n      } /* End if( days_since_1900 != deep_arg->preep) */\n\n   /* Do solar terms */\n   deep_arg->savtsn = 1E20;\n\n   /* There was previously some convoluted logic here,  but it boils    */\n   /* down to this:  we compute the solar terms,  then the lunar terms. */\n   /* On a second pass,  we recompute the solar terms,  taking advantage */\n   /* of the improved data that resulted from computing lunar terms.     */\n   for( iteration = 0; iteration < 2; iteration++)\n      {\n      const double c1l = 4.7968065E-7;\n      const double a1 = zcosg * zcosh + zsing * zcosi * zsinh;\n      const double a3 = -zsing * zcosh + zcosg * zcosi * zsinh;\n      const double a7 = -zcosg * zsinh + zsing * zcosi * zcosh;\n      const double a8 = zsing * zsini;\n      const double a9 = zsing * zsinh + zcosg * zcosi * zcosh;\n      const double a10 = zcosg * zsini;\n      const double a2 = deep_arg->cosio * a7 + deep_arg->sinio * a8;\n      const double a4 = deep_arg->cosio * a9 + deep_arg->sinio * a10;\n      const double a5 = -deep_arg->sinio * a7 + deep_arg->cosio * a8;\n      const double a6 = -deep_arg->sinio * a9 + deep_arg->cosio * a10;\n      const double x1 = a1 * deep_arg->cosg + a2 * deep_arg->sing;\n      const double x2 = a3 * deep_arg->cosg + a4 * deep_arg->sing;\n      const double x3 = -a1 * deep_arg->sing + a2 * deep_arg->cosg;\n      const double x4 = -a3 * deep_arg->sing + a4 * deep_arg->cosg;\n      const double x5 = a5 * deep_arg->sing;\n      const double x6 = a6 * deep_arg->sing;\n      const double x7 = a5 * deep_arg->cosg;\n      const double x8 = a6 * deep_arg->cosg;\n      const double z31 = 12 * x1 * x1 - 3 * x3 * x3;\n      const double z32 = 24 * x1 * x2 - 6 * x3 * x4;\n      const double z33 = 12 * x2 * x2 - 3 * x4 * x4;\n      const double z11 = -6 * a1 * a5 + deep_arg->eosq * (-24 * x1 * x7 - 6 * x3 * x5);\n      const double z12 = -6 * (a1 * a6 + a3 * a5) +  deep_arg->eosq *\n                (-24 * (x2 * x7 + x1 * x8) - 6 * (x3 * x6 + x4 * x5));\n      const double z13 = -6 * a3 * a6 + deep_arg->eosq * (-24 * x2 * x8 - 6 * x4 * x6);\n      const double z21 = 6 * a2 * a5 + deep_arg->eosq * (24 * x1 * x5 - 6 * x3 * x7);\n      const double z22 = 6 * (a4 * a5 + a2 * a6) +  deep_arg->eosq *\n                (24 * (x2 * x5 + x1 * x6) - 6 * (x4 * x7 + x3 * x8));\n      const double z23 = 6 * a4 * a6 + deep_arg->eosq * (24 * x2 * x6 - 6 * x4 * x8);\n      const double s3 = cc / deep_arg->xnq;\n      const double s2 = -0.5 * s3 / deep_arg->betao;\n      const double s4 = s3 * deep_arg->betao;\n      const double s1 = -15 * tle->eo * s4;\n      const double s5 = x1 * x3 + x2 * x4;\n      const double s6 = x2 * x3 + x1 * x4;\n      const double s7 = x2 * x4 - x1 * x3;\n      double z1 = 3 * (a1 * a1 + a2 * a2) + z31 * deep_arg->eosq;\n      double z2 = 6 * (a1 * a3 + a2 * a4) + z32 * deep_arg->eosq;\n      double z3 = 3 * (a3 * a3 + a4 * a4) + z33 * deep_arg->eosq;\n\n      z1 = z1 + z1 + deep_arg->betao2 * z31;\n      z2 = z2 + z2 + deep_arg->betao2 * z32;\n      z3 = z3 + z3 + deep_arg->betao2 * z33;\n      se = s1*zn*s5;\n      si = s2*zn*(z11+z13);\n      sl = -zn*s3*(z1+z3-14-6*deep_arg->eosq);\n      sgh = s4*zn*(z31+z33-6);\n      if( tle->xincl < pi / 60.)      /* pi / 60 radians = 3 degrees */\n         sh = 0;\n      else\n         sh = -zn*s2*(z21+z23);\n      deep_arg->ee2 = 2*s1*s6;\n      deep_arg->e3 = 2*s1*s7;\n      deep_arg->xi2 = 2*s2*z12;\n      deep_arg->xi3 = 2*s2*(z13-z11);\n      deep_arg->xl2 = -2*s3*z2;\n      deep_arg->xl3 = -2*s3*(z3-z1);\n      deep_arg->xl4 = -2*s3*(-21-9*deep_arg->eosq)*ze;\n      deep_arg->xgh2 = 2*s4*z32;\n      deep_arg->xgh3 = 2*s4*(z33-z31);\n      deep_arg->xgh4 = -18*s4*ze;\n      deep_arg->xh2 = -2*s2*z22;\n      deep_arg->xh3 = -2*s2*(z23-z21);\n\n      if( !iteration)   /* we compute lunar terms only on the first pass: */\n         {\n         deep_arg->sse = se;\n         deep_arg->ssi = si;\n         deep_arg->ssl = sl;\n         deep_arg->ssh = (deep_arg->sinio ? sh / deep_arg->sinio : 0.);\n         deep_arg->ssg = sgh-deep_arg->cosio*deep_arg->ssh;\n         deep_arg->se2 = deep_arg->ee2;\n         deep_arg->si2 = deep_arg->xi2;\n         deep_arg->sl2 = deep_arg->xl2;\n         deep_arg->sgh2 = deep_arg->xgh2;\n         deep_arg->sh2 = deep_arg->xh2;\n         deep_arg->se3 = deep_arg->e3;\n         deep_arg->si3 = deep_arg->xi3;\n         deep_arg->sl3 = deep_arg->xl3;\n         deep_arg->sgh3 = deep_arg->xgh3;\n         deep_arg->sh3 = deep_arg->xh3;\n         deep_arg->sl4 = deep_arg->xl4;\n         deep_arg->sgh4 = deep_arg->xgh4;\n         zcosg = zcosgl;\n         zsing = zsingl;\n         zcosi = zcosil;\n         zsini = zsinil;\n         zcosh = zcoshl * cosq + zsinhl * sinq;\n         zsinh = sinq * zcoshl - cosq * zsinhl;\n         zn = znl_per_min;\n         cc = c1l;\n         ze = zel;\n         }\n      }\n\n   deep_arg->sse += se;\n   deep_arg->ssi += si;\n   deep_arg->ssl += sl;\n   deep_arg->ssg += sgh;\n   if( deep_arg->sinio)\n      {\n      deep_arg->ssg -= sh * deep_arg->cosio / deep_arg->sinio;\n      deep_arg->ssh += sh / deep_arg->sinio;\n      }\n\n         /* \"if mean motion is 1.893053 to 2.117652 revs/day, and ecc >= .5\" */\n   if( deep_arg->xnq >= 0.00826 && deep_arg->xnq <= 0.00924 && tle->eo >= .5)\n      {           /* start of 12-hour orbit, e >.5 section */\n                  /* 'root##' variables are somewhat inaccurate values for */\n                  /* a few fully normalized sectorial/tesseral spherical   */\n                  /* harmonics of the Earth's gravitational potential:     */\n      const double root22 = 1.7891679E-6;\n      const double root32 = 3.7393792E-7;\n      const double root44 = 7.3636953E-9;\n      const double root52 = 1.1428639E-7;\n      const double root54 = 2.1765803E-9;\n      const double g201 = -0.306 - (tle->eo - 0.64) * 0.440;\n      const double sini2 = deep_arg->sinio*deep_arg->sinio;\n      const double f220 = 0.75*(1+2*deep_arg->cosio+deep_arg->cosio2);\n      const double f221 = 1.5 * sini2;\n      const double f321 = 1.875 * deep_arg->sinio * (1 - 2 *\\\n               deep_arg->cosio - 3 * deep_arg->cosio2);\n      const double f322 = -1.875*deep_arg->sinio*(1+2*\n               deep_arg->cosio-3*deep_arg->cosio2);\n      const double f441 = 35 * sini2 * f220;\n      const double f442 = 39.3750 * sini2 * sini2;\n      const double f522 = 9.84375*deep_arg->sinio*(sini2*(1-2*deep_arg->cosio-5*\n                 deep_arg->cosio2)+0.33333333*(-2+4*deep_arg->cosio+\n                 6*deep_arg->cosio2));\n      const double f523 = deep_arg->sinio*(4.92187512*sini2*(-2-4*\n                 deep_arg->cosio+10*deep_arg->cosio2)+6.56250012\n                 *(1+2*deep_arg->cosio-3*deep_arg->cosio2));\n      const double f542 = 29.53125*deep_arg->sinio*(2-8*\n                 deep_arg->cosio+deep_arg->cosio2*\n                 (-12+8*deep_arg->cosio+10*deep_arg->cosio2));\n      const double f543 = 29.53125*deep_arg->sinio*(-2-8*deep_arg->cosio+\n                 deep_arg->cosio2*(12+8*deep_arg->cosio-10*\n                 deep_arg->cosio2));\n      double g410, g422, g520, g521, g532, g533;\n      double g211, g310, g322;\n      double temp, temp1;\n\n      deep_arg->resonance_flag = 1;       /* it _is_ resonant... */\n      deep_arg->synchronous_flag = 0;     /* but it's not synchronous */\n             /* Geopotential resonance initialization for 12 hour orbits: */\n      if (tle->eo <= 0.65)\n         {\n         g211 = 3.616-13.247*tle->eo+16.290*deep_arg->eosq;\n         g310 = eval_cubic_poly( tle->eo, -19.302, 117.390, -228.419, 156.591);\n         g322 = eval_cubic_poly( tle->eo, -18.9068, 109.7927, -214.6334, 146.5816);\n         g410 = eval_cubic_poly( tle->eo, -41.122, 242.694, -471.094, 313.953);\n         g422 = eval_cubic_poly( tle->eo, -146.407, 841.880, -1629.014, 1083.435);\n         g520 = eval_cubic_poly( tle->eo, -532.114, 3017.977, -5740.032, 3708.276);\n                               /* NOTE: quadratic coeff was 5740 */\n         }\n      else\n         {\n         g211 = eval_cubic_poly( tle->eo, -72.099, 331.819, -508.738, 266.724);\n         g310 = eval_cubic_poly( tle->eo, -346.844, 1582.851, -2415.925, 1246.113);\n         g322 = eval_cubic_poly( tle->eo, -342.585, 1554.908, -2366.899, 1215.972);\n         g410 = eval_cubic_poly( tle->eo, -1052.797, 4758.686, -7193.992, 3651.957);\n         g422 = eval_cubic_poly( tle->eo, -3581.69, 16178.11, -24462.77, 12422.52);\n         if (tle->eo <= 0.715)\n            g520 = eval_cubic_poly( tle->eo, 1464.74, -4664.75, 3763.64, 0.);\n         else\n            g520 = eval_cubic_poly( tle->eo, -5149.66, 29936.92, -54087.36, 31324.56);\n         } /* End if (tle->eo <= 0.65) */\n\n      if (tle->eo < 0.7)\n         {\n         g533 = eval_cubic_poly( tle->eo, -919.2277, 4988.61, -9064.77, 5542.21);\n         g521 = eval_cubic_poly( tle->eo, -822.71072, 4568.6173, -8491.4146, 5337.524);\n         g532 = eval_cubic_poly( tle->eo, -853.666, 4690.25, -8624.77, 5341.4);\n         }\n      else\n         {\n         g533 = eval_cubic_poly( tle->eo, -37995.78, 161616.52, -229838.2, 109377.94);\n         g521 = eval_cubic_poly( tle->eo, -51752.104, 218913.95, -309468.16, 146349.42);\n         g532 = eval_cubic_poly( tle->eo, -40023.88, 170470.89, -242699.48, 115605.82);\n         } /* End if (tle->eo <= 0.7) */\n\n      temp1 = 3 * deep_arg->xnq * deep_arg->xnq * aqnv * aqnv;\n      temp = temp1*root22;\n      deep_arg->d2201 = temp * f220 * g201;\n      deep_arg->d2211 = temp * f221 * g211;\n      temp1 *= aqnv;\n      temp = temp1*root32;\n      deep_arg->d3210 = temp * f321 * g310;\n      deep_arg->d3222 = temp * f322 * g322;\n      temp1 *= aqnv;\n      temp = 2*temp1*root44;\n      deep_arg->d4410 = temp * f441 * g410;\n      deep_arg->d4422 = temp * f442 * g422;\n      temp1 *= aqnv;\n      temp = temp1*root52;\n      deep_arg->d5220 = temp * f522 * g520;\n      deep_arg->d5232 = temp * f523 * g532;\n      temp = 2*temp1*root54;\n      deep_arg->d5421 = temp * f542 * g521;\n      deep_arg->d5433 = temp * f543 * g533;\n      deep_arg->xlamo = tle->xmo+tle->xnodeo+tle->xnodeo-deep_arg->thgr-deep_arg->thgr;\n      bfact = deep_arg->xmdot + deep_arg->xnodot+\n                   deep_arg->xnodot - thdt - thdt;\n      bfact += deep_arg->ssl + deep_arg->ssh + deep_arg->ssh;\n      }        /* end of 12-hour orbit, e >.5 section */\n   else if( deep_arg->xnq < 1.2 * twopi / minutes_per_day &&\n            deep_arg->xnq > 0.8 * twopi / minutes_per_day)\n      {                        /* \"if mean motion is .8 to 1.2 revs/day\" */\n      const double q22    =  1.7891679E-6;\n      const double q31    =  2.1460748E-6;\n      const double q33    =  2.2123015E-7;\n      const double cosio_plus_1 = 1. + deep_arg->cosio;\n      const double g200 = 1+deep_arg->eosq*(-2.5+0.8125*deep_arg->eosq);\n      const double g300 = 1+deep_arg->eosq*(-6+6.60937*deep_arg->eosq);\n      const double f311 = 0.9375*deep_arg->sinio*deep_arg->sinio*\n             (1+3*deep_arg->cosio)-0.75*cosio_plus_1;\n      const double g310 = 1+2*deep_arg->eosq;\n      const double f220 = 0.75 * cosio_plus_1 * cosio_plus_1;\n      const double f330 = 2.5 * f220 * cosio_plus_1;\n\n      deep_arg->resonance_flag = deep_arg->synchronous_flag = 1;\n      /* Synchronous resonance terms initialization */\n      deep_arg->del1 = 3*deep_arg->xnq*deep_arg->xnq*aqnv*aqnv;\n      deep_arg->del2 = 2*deep_arg->del1*f220*g200*q22;\n      deep_arg->del3 = 3*deep_arg->del1*f330*g300*q33*aqnv;\n      deep_arg->del1 *= f311*g310*q31*aqnv;\n      deep_arg->xlamo = tle->xmo+tle->xnodeo+tle->omegao-deep_arg->thgr;\n      bfact = deep_arg->xmdot + deep_arg->omgdot + deep_arg->xnodot - thdt;\n      bfact = bfact+deep_arg->ssl+deep_arg->ssg+deep_arg->ssh;\n      } /* End of geosych case */\n   else              /* it's neither a high-e 12-hr orbit nor a geosynch: */\n      deep_arg->resonance_flag = deep_arg->synchronous_flag = 0;\n\n   if( deep_arg->resonance_flag)\n      {\n      deep_arg->xfact = bfact-deep_arg->xnq;\n\n      /* Initialize integrator */\n      deep_arg->xli = deep_arg->xlamo;\n      deep_arg->xni = deep_arg->xnq;\n      deep_arg->atime = 0;\n      }\n   /* End case dpinit: */\n}\n\n/* 'dpsec' is unavoidably confusing.  See https://projectpluto.com/dpsec.htm\nfor some commentary on what's going on here. */\n\nstatic inline void compute_dpsec_derivs( const deep_arg_t *deep_arg,\n                                           double *derivs)\n{\n   const double sin_li = sin( deep_arg->xli);\n   const double cos_li = cos( deep_arg->xli);\n   const double sin_2li = 2. * sin_li * cos_li;\n   const double cos_2li = 2. * cos_li * cos_li - 1.;\n   int i;\n\n   derivs[0] = 0.;\n                /* Dot terms calculated,  using a lot of trig add/subtract */\n                /* identities to reduce the computational load... at the   */\n                /* cost of making the code somewhat hard to follow:        */\n   if( deep_arg->synchronous_flag )\n      {\n/*    const double fasx2 = 0.1313091 radians =   7.523456 degrees */\n/*    const double fasx4 = 2.8843198 radians = 165.259351 degrees */\n/*    const double fasx6 = 0.3744809 radians =  21.456173 degrees */\n      const double c_fasx2 =  0.99139134268488593;\n      const double s_fasx2 =  0.13093206501640101;\n      const double c_2fasx4 =  0.87051638752972937;\n      const double s_2fasx4 = -0.49213943048915526;\n      const double c_3fasx6 =  0.43258117585763334;\n      const double s_3fasx6 =  0.90159499016666422;\n      const double sin_3li = sin_2li * cos_li + cos_2li * sin_li;\n      const double cos_3li = cos_2li * cos_li - sin_2li * sin_li;\n      double term1a = deep_arg->del1 * (sin_li  * c_fasx2  - cos_li  * s_fasx2);\n      double term2a = deep_arg->del2 * (sin_2li * c_2fasx4 - cos_2li * s_2fasx4);\n      double term3a = deep_arg->del3 * (sin_3li * c_3fasx6 - cos_3li * s_3fasx6);\n      double term1b = deep_arg->del1 * (cos_li  * c_fasx2  + sin_li  * s_fasx2);\n      double term2b = 2. * deep_arg->del2 * (cos_2li * c_2fasx4 + sin_2li * s_2fasx4);\n      double term3b = 3. * deep_arg->del3 * (cos_3li * c_3fasx6 + sin_3li * s_3fasx6);\n\n      for( i = 0; i < dpsec_integration_order; i += 2)\n         {\n         *derivs++ = term1a + term2a + term3a;\n         *derivs++ = term1b + term2b + term3b;\n         if( i + 2 < dpsec_integration_order)\n            {\n            term1a = -term1a;\n            term2a *= -4.;\n            term3a *= -9.;\n            term1b = -term1b;\n            term2b *= -4.;\n            term3b *= -9.;\n            }\n         }\n      }        /* end of geosynch case */\n   else\n      {        /* orbit is a 12-hour resonant one: */\n/*    const double g22    =  5.7686396;      */\n/*    const double g32    =  0.95240898;     */\n/*    const double g44    =  1.8014998;      */\n/*    const double g52    =  1.0508330;      */\n/*    const double g54    =  4.4108898;      */\n      const double c_g22 =  0.87051638752972937;\n      const double s_g22 = -0.49213943048915526;\n      const double c_g32 =  0.57972190187001149;\n      const double s_g32 =  0.81481440616389245;\n      const double c_g44 = -0.22866241528815548;\n      const double s_g44 =  0.97350577801807991;\n      const double c_g52 =  0.49684831179884198;\n      const double s_g52 =  0.86783740128127729;\n      const double c_g54 = -0.29695209575316894;\n      const double s_g54 = -0.95489237761529999;\n      const double xomi =\n                deep_arg->omegaq + deep_arg->omgdot * deep_arg->atime;\n      const double sin_omi = sin( xomi), cos_omi = cos( xomi);\n      const double sin_li_m_omi = sin_li * cos_omi - sin_omi * cos_li;\n      const double sin_li_p_omi = sin_li * cos_omi + sin_omi * cos_li;\n      const double cos_li_m_omi = cos_li * cos_omi + sin_omi * sin_li;\n      const double cos_li_p_omi = cos_li * cos_omi - sin_omi * sin_li;\n      const double sin_2omi = 2. * sin_omi * cos_omi;\n      const double cos_2omi = 2. * cos_omi * cos_omi - 1.;\n      const double sin_2li_m_omi = sin_2li * cos_omi - sin_omi * cos_2li;\n      const double sin_2li_p_omi = sin_2li * cos_omi + sin_omi * cos_2li;\n      const double cos_2li_m_omi = cos_2li * cos_omi + sin_omi * sin_2li;\n      const double cos_2li_p_omi = cos_2li * cos_omi - sin_omi * sin_2li;\n      const double sin_2li_p_2omi = sin_2li * cos_2omi + sin_2omi * cos_2li;\n      const double cos_2li_p_2omi = cos_2li * cos_2omi - sin_2omi * sin_2li;\n      const double sin_2omi_p_li = sin_li * cos_2omi + sin_2omi * cos_li;\n      const double cos_2omi_p_li = cos_li * cos_2omi - sin_2omi * sin_li;\n      double term1a =\n               deep_arg->d2201 * (sin_2omi_p_li*c_g22 - cos_2omi_p_li*s_g22)\n             + deep_arg->d2211 * (sin_li * c_g22 - cos_li * s_g22)\n             + deep_arg->d3210 * (sin_li_p_omi*c_g32 - cos_li_p_omi*s_g32)\n             + deep_arg->d3222 * (sin_li_m_omi*c_g32 - cos_li_m_omi*s_g32)\n             + deep_arg->d5220 * (sin_li_p_omi*c_g52 - cos_li_p_omi*s_g52)\n             + deep_arg->d5232 * (sin_li_m_omi*c_g52 - cos_li_m_omi*s_g52);\n      double term2a =\n               deep_arg->d4410 * (sin_2li_p_2omi*c_g44 - cos_2li_p_2omi*s_g44)\n             + deep_arg->d4422 * (sin_2li * c_g44 - cos_2li * s_g44)\n             + deep_arg->d5421 * (sin_2li_p_omi*c_g54 - cos_2li_p_omi*s_g54)\n             + deep_arg->d5433 * (sin_2li_m_omi*c_g54 - cos_2li_m_omi*s_g54);\n      double term1b =\n              (deep_arg->d2201 * (cos_2omi_p_li*c_g22 + sin_2omi_p_li*s_g22)\n             + deep_arg->d2211 * (cos_li * c_g22 + sin_li * s_g22)\n             + deep_arg->d3210 * (cos_li_p_omi*c_g32 + sin_li_p_omi*s_g32)\n             + deep_arg->d3222 * (cos_li_m_omi*c_g32 + sin_li_m_omi*s_g32)\n             + deep_arg->d5220 * (cos_li_p_omi*c_g52 + sin_li_p_omi*s_g52)\n             + deep_arg->d5232 * (cos_li_m_omi*c_g52 + sin_li_m_omi*s_g52));\n      double term2b = 2. *\n              (deep_arg->d4410 * (cos_2li_p_2omi*c_g44 + sin_2li_p_2omi*s_g44)\n             + deep_arg->d4422 * (cos_2li * c_g44 + sin_2li * s_g44)\n             + deep_arg->d5421 * (cos_2li_p_omi*c_g54 + sin_2li_p_omi*s_g54)\n             + deep_arg->d5433 * (cos_2li_m_omi*c_g54 + sin_2li_m_omi*s_g54));\n\n      for( i = 0; i < dpsec_integration_order; i += 2)\n         {\n         *derivs++ = term1a + term2a;\n         *derivs++ = term1b + term2b;\n         if( i + 2 < dpsec_integration_order)\n            {\n            term1a = -term1a;\n            term2a *= -4.;\n            term1b = -term1b;\n            term2b *= -4.;\n            }\n         }\n      } /* End of 12-hr resonant case */\n}\n\nvoid Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg)\n{\n   double temp, xni, xli;\n   int final_integration_step = 0;\n\n   deep_arg->xll += deep_arg->ssl*deep_arg->t;\n   deep_arg->omgadf += deep_arg->ssg*deep_arg->t;\n   deep_arg->xnode += deep_arg->ssh*deep_arg->t;\n   deep_arg->em = tle->eo+deep_arg->sse*deep_arg->t;\n   deep_arg->xinc = tle->xincl+deep_arg->ssi*deep_arg->t;\n   if( !deep_arg->resonance_flag ) return;\n\n            /* If we're closer to t=0 than to the currently-stored data\n               from the previous call to this function,  then we're\n               better off \"restarting\",  going back to the initial data.\n               The Dundee code rigs things up to _always_ take 720-minute\n               steps from epoch to end time,  except for the final step.\n               So if we'd have to integrate \"backwards\" (toward the epoch),\n               we gotta do a restart if we're to be Dundee-compliant.  */\n   if( fabs( deep_arg->t) < fabs( deep_arg->t - deep_arg->atime)\n            || (is_dundee_compliant && fabs( deep_arg->t) < fabs( deep_arg->atime)))\n      {                                    /* Epoch restart */\n      deep_arg->atime = 0.;\n      xni = deep_arg->xnq;\n      xli = deep_arg->xlamo;\n      }\n   else                          /* use xni, xli from previous runs: */\n      {\n      xni = deep_arg->xni;\n      xli = deep_arg->xli;\n      }\n\n   while( !final_integration_step)\n      {\n      double xldot, derivs[20], xlpow = 1., delt_factor;\n      double delt = deep_arg->t - deep_arg->atime;\n      int i;\n\n      deep_arg->xni = xni;\n      deep_arg->xli = xli;\n      compute_dpsec_derivs( deep_arg, derivs);\n      if( delt > dpsec_integration_step)\n         delt = dpsec_integration_step;\n      else if( delt < -dpsec_integration_step)\n         delt = -dpsec_integration_step;\n      else\n         final_integration_step = 1;\n\n      xldot = xni+deep_arg->xfact;\n\n      xli += delt * xldot;\n      xni += delt * derivs[0];\n      delt_factor = delt;\n      for( i = 2; i <= dpsec_integration_order; i++)\n         {\n         xlpow *= xldot;\n         derivs[i - 1] *= xlpow;\n         delt_factor *= delt / (double)i;\n         xli += delt_factor * derivs[i - 2];\n         xni += delt_factor * derivs[i - 1];\n         }\n      if( !is_dundee_compliant || !final_integration_step)\n         {\n         deep_arg->xni = xni;\n         deep_arg->xli = xli;\n         deep_arg->atime += delt;\n         }\n      }\n\n   deep_arg->xn = xni;\n\n   temp = -deep_arg->xnode + deep_arg->thgr + deep_arg->t * thdt;\n\n   deep_arg->xll = xli + temp\n         + (deep_arg->synchronous_flag ? -deep_arg->omgadf : temp);\n   /*End case dpsec: */\n}\n\nvoid Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg)\n{\n   double sinis, cosis;\n\n            /* If the time didn't change by more than 30 minutes,      */\n            /* there's no good reason to recompute the perturbations;  */\n            /* they don't change enough over so short a time span.     */\n            /* However,  the Dundee code _always_ recomputes,  so if   */\n            /* we're attempting to replicate its results,  we've gotta */\n            /* recompute everything,  too.                             */\n   if( fabs(deep_arg->savtsn-deep_arg->t) >= 30. || is_dundee_compliant)\n      {\n      double zf, zm, sinzf, ses, sis, sil, sel, sll, sls;\n      double f2, f3, sghl, sghs, shs, sh1;\n\n      deep_arg->savtsn = deep_arg->t;\n\n            /* Update solar perturbations for time T: */\n      zm = deep_arg->zmos+zns_per_min*deep_arg->t;\n      zf = zm+2*zes*sin(zm);\n      sinzf = sin(zf);\n      f2 = 0.5*sinzf*sinzf-0.25;\n      f3 = -0.5*sinzf*cos(zf);\n      ses = deep_arg->se2*f2+deep_arg->se3*f3;\n      sis = deep_arg->si2*f2+deep_arg->si3*f3;\n      sls = deep_arg->sl2*f2+deep_arg->sl3*f3+deep_arg->sl4*sinzf;\n      sghs = deep_arg->sgh2*f2+deep_arg->sgh3*f3+deep_arg->sgh4*sinzf;\n      shs = deep_arg->sh2*f2+deep_arg->sh3*f3;\n\n            /* Update lunar perturbations for time T: */\n      zm = deep_arg->zmol+znl_per_min*deep_arg->t;\n      zf = zm+2*zel*sin(zm);\n      sinzf = sin(zf);\n      f2 = 0.5*sinzf*sinzf-0.25;\n      f3 = -0.5*sinzf*cos(zf);\n      sel = deep_arg->ee2*f2+deep_arg->e3*f3;\n      sil = deep_arg->xi2*f2+deep_arg->xi3*f3;\n      sll = deep_arg->xl2*f2+deep_arg->xl3*f3+deep_arg->xl4*sinzf;\n      sghl = deep_arg->xgh2*f2+deep_arg->xgh3*f3+deep_arg->xgh4*sinzf;\n      sh1 = deep_arg->xh2*f2+deep_arg->xh3*f3;\n\n            /* Sum the solar and lunar contributions: */\n      deep_arg->pe = ses+sel;\n      deep_arg->pinc = sis+sil;\n      deep_arg->pl = sls+sll;\n      deep_arg->pgh = sghs+sghl;\n      deep_arg->ph = shs+sh1;\n#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH\n      if( deep_arg->solar_lunar_init_flag)\n         {\n         deep_arg->pe0 = deep_arg->pe;\n         deep_arg->pinc0 = deep_arg->pinc;\n         deep_arg->pl0 = deep_arg->pl;\n         deep_arg->pgh0 = deep_arg->pgh;\n         deep_arg->ph0 = deep_arg->ph;\n         }\n      deep_arg->pe  -= deep_arg->pe0;\n      deep_arg->pinc -= deep_arg->pinc0;\n      deep_arg->pl  -= deep_arg->pl0;\n      deep_arg->pgh -= deep_arg->pgh0;\n      deep_arg->ph  -= deep_arg->ph0;\n      if( deep_arg->solar_lunar_init_flag)\n         return;        /* done all we really need to do here... */\n#endif\n      }\n\n               /* In Spacetrack 3, sinis & cosis were initialized       */\n               /* _before_ perturbations were added to xinc.  In        */\n               /* Spacetrack 6,  it's the other way around (see below). */\n#ifndef SPACETRACK_3\n   deep_arg->xinc += deep_arg->pinc;\n#endif\n   sinis = sin( deep_arg->xinc);\n   cosis = cos( deep_arg->xinc);\n#ifdef SPACETRACK_3\n   deep_arg->xinc += deep_arg->pinc;\n#endif\n\n         /* Add solar/lunar perturbation correction to eccentricity: */\n   deep_arg->em += deep_arg->pe;\n   deep_arg->xll += deep_arg->pl;\n   deep_arg->omgadf += deep_arg->pgh;\n   if( tle->xincl >= 0.2)\n      {             /* Apply periodics directly */\n      double temp_val;\n\n#ifdef SPACETRACK_3\n      sinis = sin(deep_arg->xinc);\n      cosis = cos(deep_arg->xinc);\n#endif\n      temp_val = deep_arg->ph / sinis;\n      deep_arg->omgadf -= cosis * temp_val;\n      deep_arg->xnode += temp_val;\n      }\n   else\n      {\n        /* Apply periodics with Lyddane modification */\n      const double sinok = sin(deep_arg->xnode);\n      const double cosok = cos(deep_arg->xnode);\n      const double alfdp = deep_arg->ph * cosok\n                    + (deep_arg->pinc * cosis + sinis) * sinok;\n      const double betdp = - deep_arg->ph * sinok\n                    + (deep_arg->pinc * cosis + sinis) * cosok;\n      double dls, delta_xnode;\n\n//    deep_arg->xnode = FMod2p(deep_arg->xnode);\n      delta_xnode = atan2(alfdp,betdp) - deep_arg->xnode;\n\n       /* This is a patch to Lyddane modification suggested */\n       /* by Rob Matson, streamlined very slightly by BJG, to */\n       /* keep 'delta_xnode' between +/- 180 degrees: */\n\n      if( delta_xnode < - pi)\n         delta_xnode += twopi;\n      else if( delta_xnode > pi)\n         delta_xnode -= twopi;\n\n      dls = -deep_arg->xnode * sinis * deep_arg->pinc;\n#ifdef SPACETRACK_3\n      deep_arg->omgadf += dls\n               + cosis * deep_arg->xnode -\n               - cos( deep_arg->xinc) * (deep_arg->xnode + delta_xnode);\n#else\n      deep_arg->omgadf += dls - cosis * delta_xnode;\n#endif\n      deep_arg->xnode += delta_xnode;\n      } /* End case dpper: */\n}\n"
  },
  {
    "path": "dropouts.c",
    "content": "/* Code to check for the existence of certain artsats in Space-Track's\nmaster TLE list.  Occasionally,  they've dropped objects and I didn't\nrealize it.  The objects ended up on NEOCP and I didn't ID them as\nquickly as might be desired,  because I assumed they must be \"new\".\nThis should warn me if certain artsats get dropped from 'all_tle.txt'.\n\n   The absence of certain artsats is essentially routine.  But for some\nobjects (marked with an !),  Space-Track is our only source of TLEs.\n(Or at least,  I've been relying on them.  I _could_ generate TLES for\nCXO,  for example,  based on _Horizons_ ephems.  Since I don't,  I want\nthis program to squawk loudly if Space-Track stops supplying CXO TLEs.)\n\n   As of 2024 Aug 31,  the program can also be used for updating the\nSpace-Track TLEs in a slightly more cautious manner.  If you have\ndownloaded new TLEs as the (default) ALL_TLE.TXT,  and your \"usual\"\nTLEs are at all_tle.txt,  then\n\n./dropouts ALL_TLE.TXT 25000 all_tle.txt\n\n   will check to see if ALL_TLE.TXT actually has 25000 or more TLEs in it.\nIf it does,  the download is presumed to have succeeded;  all_tle.txt is\nunlinked and replaced with ALL_TLE.TXT.  If it fails,  we leave both files\nundisturbed.\n\n   This should help in the increasingly frequent situations where new\nTLE files are downloaded and then have only an error message,  or a\ndrastically reduced number of TLEs.        */\n\n#include <stdio.h>\n#include <assert.h>\n#include <string.h>\n#include <stdlib.h>\n#ifdef _WIN32\n   #include <io.h>\n#else\n   #include <unistd.h>\n#endif\n\n#define VT_NORMAL        \"\\033[0m\"\n#define VT_REVERSE       \"\\033[7m\"\n\nint main( const int argc, const char **argv)\n{\n   static const char *sats[] = {\n          \"00041A ! Cluster II-FM7\",\n          \"00045A   Cluster II-FM5\",\n          \"00045B ! Cluster II-FM8\",\n          \"02048A ! INTEGRAL\",\n          \"07004A ! THEMIS-A\",\n          \"07004D ! THEMIS-D\",\n          \"07004E ! THEMIS-E\",\n          \"09017B ! Atlas 5 Centaur R/B\",\n          \"09068B ! Delta 4 R/B\",\n          \"12003B ! Delta 4 R/B\",\n          \"12011B ! Breeze-M R/B\",\n          \"13024B ! WGS-5 R/B\",\n          \"13026B ! Breeze-M R/B\",\n          \"15005B ! Inmarsat 5F2 booster\",\n          \"15011A ! MMS 1\",\n          \"15011B ! MMS 2\",\n          \"15011C ! MMS 3\",\n          \"15011D ! MMS 4\",\n          \"15019C ! Yuanzheng-1 Y1\",\n          \"15042B ! Breeze-M R/B\",\n          \"16041A ! MUOS 5\",\n          \"18038A   TESS\",\n          \"22110B ! Ariane 5 R/B\",\n          \"22146B ! Falcon 9 R/B\",\n          \"22134B ! Falcon 9 R/B\",\n          \"24048E   DRO R/B\",\n          \"24059B ! Falcon 9 R/B\",\n          \"24127B ! Falcon 9 R/B\",\n          \"24233A ! Proba-3\",\n          \"24233B ! Proba-3 booster\",\n          \"63039A   Vela 1A\",\n          \"64040B   Vela 2B\",\n          \"65058A   Vela 3A\",\n          \"65058B   Vela 6\",\n          \"67040A   Vela 4A\",\n          \"67040F ! Titan 3C transtage booster\",\n          \"69046F ! Titan 3C transtage booster\",\n          \"69046G   Vela 9/10 interstage\",\n          \"70027C ! Vela 6 booster\",\n          \"72073A   IMP-7\",\n          \"76023C ! SOLRAD-11A\",\n          \"76023H ! SOLRAD-11 debris\",\n          \"77093E   SL-6 R/B(2)\",\n          \"83020A ! ASTRON\",\n          \"83020D ! ASTRON booster\",\n          \"92044A   GEOTAIL\",\n          \"95062A ! ISO\",\n          \"95062C ! ISO debris\",\n          \"97075B ! Equator S\",\n          \"99040B ! Chandra X-Ray Observatory\",\n          \"99040D ! IUS (for CXO)\",\n          \"99066A ! XMM/Newton\",\n          \"99066B ! XMM/Newton booster\",\n          NULL };\n   FILE *ifile = fopen( argc == 1 ? \"all_tle.txt\" : argv[1], \"rb\");\n   char buff[100];\n   size_t i;\n   int trouble_found = 0, n_found = 0;\n\n   assert( ifile);\n   while( fgets( buff, sizeof( buff), ifile))\n      if( *buff == '1' && buff[1] == ' ' && buff[7] == 'U')\n         {\n         size_t len = strlen( buff);\n\n         while( len && buff[len - 1] < ' ')\n            len--;\n         if( 69 == len)\n            {\n            n_found++;\n            for( i = 0; sats[i]; i++)\n               if( sats[i][0] == buff[9] && !memcmp( sats[i], buff + 9, 7))\n                  sats[i] = \"\";\n            }\n         }\n   fclose( ifile);\n   printf( \"This will list high-flying artsats for which TLEs are not provided :\\n\");\n   for( i = 0; sats[i]; i++)\n      if( sats[i][0])\n         {\n         printf( \"%s\\n\", sats[i]);\n         if( sats[i][7] == '!')\n            {\n            trouble_found = 1;\n            printf( VT_REVERSE);\n            printf( \"DANGER!!! We do NOT have an independent source of TLEs\\n\");\n            printf( \"for this object.  Please report to \"\n                        \"pluto\\x40\\x70roject\\x70luto\\x2e\\x63om.\\n\");\n                   /* Above is (slightly) obfuscated address to foil spambots */\n            printf( \"This needs to be fixed.\\n\");\n            printf( VT_NORMAL);\n            }\n         }\n   if( !trouble_found)\n      printf( \"Any missing objects are covered by other sources.  Nothing\\n\"\n              \"to worry about here.\\n\");\n   printf( \"%d found\\n\", n_found);\n   if( 4 == argc && n_found > atoi( argv[2]))\n      {\n      printf( \"Replacing '%s' with '%s'\\n\", argv[3], argv[1]);\n#ifdef _WIN32\n      _unlink( argv[3]);\n#else\n      unlink( argv[3]);\n#endif\n      rename( argv[1], argv[3]);\n      }\n   return( 0);\n}\n"
  },
  {
    "path": "dynamic.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/* Hooks to allow the satellite code to be accessed from a DLL,\nwith the DLL not loaded at startup;  instead,  LoadLibrary is\nused at the time you decide you actually want satellite functions.\nNot something likely to be useful to many people.  I used it some\nyears back for my desktop planetarium software;  I could check for\nthe existence of the DLL,  use it if available,  or fall back to\nsome built-in code if it wasn't.  (The DLL was,  by standards of\nthe day,  a little bit large.  Not everybody had enough interest\nin artsats to download it.)   */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"windows.h\"\n#include \"norad.h\"\n\ntypedef void (__stdcall *sxpx_init_fn)( double *params, const tle_t *tle);\ntypedef int (__stdcall *sxpx_fn)( const double tsince, const tle_t *tle,\n                     const double *params, double *pos, double *vel);\ntypedef long (__stdcall *sxpx_version_fn)( void);\n\nstatic HINSTANCE load_sat_code_lib( const int unload)\n{\n   static HINSTANCE h_sat_code_lib = (HINSTANCE)0;\n   static int first_time = 1;\n\n   if( unload)\n      {\n      if( h_sat_code_lib)\n         FreeLibrary( h_sat_code_lib);\n      h_sat_code_lib = NULL;\n      first_time = 1;\n      }\n   else if( first_time)\n      {\n      h_sat_code_lib = LoadLibrary( \"sat_code.dll\");\n      first_time = 0;\n      }\n   return( h_sat_code_lib);\n}\n\n/* 26 Nov 2002:  revised following two functions slightly so that the\n   return values distinguish between \"didn't get the function\" and\n   \"didn't get the library\" */\n\nint SXPX_init( double *params, const tle_t *tle, const int sxpx_num)\n{\n   static sxpx_init_fn func[5];\n   static char already_done[5];\n   int rval = 0;\n   HINSTANCE h_sat_code_lib;\n\n   if( !params)         /* flag to unload library */\n      {\n      int i;\n\n      load_sat_code_lib( -1);\n      for( i = 0; i < 5; i++)\n         already_done[i] = 0;\n      return( 0);\n      }\n   h_sat_code_lib = load_sat_code_lib( 0);\n   if( !already_done[sxpx_num])\n      {\n      if( h_sat_code_lib)\n         func[sxpx_num] = (sxpx_init_fn)GetProcAddress( h_sat_code_lib,\n                                (LPCSTR)( sxpx_num + 1));\n      already_done[sxpx_num] = 1;\n      }\n   if( func[sxpx_num])\n      (*func[sxpx_num])( params, tle);\n   else\n      rval = -1;\n   if( !h_sat_code_lib)\n      rval = -2;\n   return( rval);\n}\n\nint SXPX( const double tsince, const tle_t *tle, const double *params,\n                               double *pos, double *vel, const int sxpx_num)\n{\n   static sxpx_fn func[5];\n   static char already_done[5];\n   int rval = 0;\n   HINSTANCE h_sat_code_lib = load_sat_code_lib( 0);\n\n   if( !already_done[sxpx_num])\n      {\n      if( h_sat_code_lib)\n         func[sxpx_num] = (sxpx_fn)GetProcAddress( h_sat_code_lib,\n                                (LPCSTR)( sxpx_num + 6));\n      already_done[sxpx_num] = 1;\n      }\n   if( func[sxpx_num])\n      rval = (*func[sxpx_num])( tsince, tle, params, pos, vel);\n   else\n      rval = -1;\n   if( !h_sat_code_lib)\n      rval = -2;\n   return( rval);\n}\n\nlong get_sat_code_lib_version( void)\n{\n   HINSTANCE h_sat_code_lib = load_sat_code_lib( 0);\n   long rval;\n\n   if( !h_sat_code_lib)\n      rval = -2;\n   else\n      {\n      sxpx_version_fn func =\n               (sxpx_version_fn)GetProcAddress( h_sat_code_lib, (LPCSTR)20);\n\n      if( !func)\n         rval = -1;\n      else\n         rval = (*func)( );\n      }\n   return( rval);\n}\n"
  },
  {
    "path": "elem2tle.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/* MOSTLY OBSOLETE.  See 'eph2tle.cpp' in the Find_Orb project\n(https://github.com/Bill-Gray/find_orb) for a considerably better\napproach to computing TLEs from orbital data.\n\n#include <stdio.h>\n#include <assert.h>\n#include <string.h>\n#include <stdlib.h>\n#include <math.h>\n#include \"watdefs.h\"\n#include \"afuncs.h\"\n#include \"comets.h\"\n#include \"norad.h\"\n#include \"norad_in.h\"   /* for xke definition */\n#include \"date.h\"\n\nconst double earth_mass_over_sun_mass = 2.98994e-6;\n#define GAUSS_K .01720209895\n#define SOLAR_GM (GAUSS_K * GAUSS_K)\n#define PI 3.141592653589793238462643383279502884197169399375105\n\nint write_tle_from_vector( char *buff, const double *state_vect,\n        const double epoch, const char *norad_desig, const char *intl_desig);\n\nint verbose = 0;\n\nstatic void set_tle_defaults( tle_t *tle)\n{\n   memset( tle, 0, sizeof( tle_t));\n   strcpy( tle->intl_desig, \"56999ZZ \");\n   tle->classification = 'U';\n   tle->ephemeris_type = '0';\n}\n\n#define centralize_angle(x) (fmod( (x) + PI * 10., PI + PI))\n\nint vector_to_tle( tle_t *tle, const double *state_vect)\n{\n   ELEMENTS elem;\n   int rval = 0, i;\n   double tvect[6];\n   const double max_ecc = .9999;\n\n   for( i = 0; i < 6; i++)      /* cvt from km, km/min to AU, AU/day */\n      tvect[i] = state_vect[i];\n   elem.gm = xke * xke * earth_radius_in_km * earth_radius_in_km * earth_radius_in_km;\n   calc_classical_elements( &elem, tvect, tle->epoch, 1);\n   tle->xincl = centralize_angle( elem.incl);\n   tle->xnodeo = centralize_angle( elem.asc_node);\n   tle->omegao = centralize_angle( elem.arg_per);\n   tle->xmo = centralize_angle( elem.mean_anomaly);\n\n   if( elem.ecc > max_ecc || elem.major_axis <= 0.)\n      rval = -1;\n   else\n      {\n      tle->eo = elem.ecc;\n      tle->xno = 1. / elem.t0;      /* xno is now in radians per minute */\n      rval = 0;\n      }\n   if( tle->xincl < 0.)\n      {\n      tle->xincl = -tle->xincl;\n      tle->xnodeo = centralize_angle( tle->xnodeo + PI);;\n      tle->omegao = centralize_angle( tle->omegao + PI);;\n      }\n   return( rval);\n}\n\nstatic void show_results( const char *title, const tle_t *tle, const double *state_vect)\n{\n   if( title)\n      printf( \"%s\\n\", title);\n   if( tle)\n      {\n      char buff[200];\n\n      write_elements_in_tle_format( buff, tle);\n      printf( \"%s\", buff);\n      }\n   printf(\"    %16.8f %16.8f %16.8f \\n\", state_vect[0], state_vect[1],\n                                         state_vect[2]);\n   printf(\"    %16.8f %16.8f %16.8f \\n\", state_vect[3] / 60.,\n                                                state_vect[4] / 60.,\n                                                state_vect[5] / 60.);\n}\n\nstatic int compute_new_state_vect( const tle_t *tle, double *state_vect,\n                     const int ephem)\n{\n   double sat_params[N_SAT_PARAMS];\n   int rval = 0;\n\n   switch( ephem)\n      {\n      case 0:\n         SGP_init( sat_params, tle);\n         rval = SGP( 0., tle, sat_params, state_vect, state_vect + 3);\n         break;\n      case 1:\n         SGP4_init( sat_params, tle);\n         rval = SGP4( 0., tle, sat_params, state_vect, state_vect + 3);\n         break;\n      case 2:\n         SGP8_init( sat_params, tle);\n         rval = SGP8( 0., tle, sat_params, state_vect, state_vect + 3);\n         break;\n      case 3:\n         SDP4_init( sat_params, tle);\n         rval = SDP4( 0., tle, sat_params, state_vect, state_vect + 3);\n         break;\n      case 4:\n         SDP8_init( sat_params, tle);\n         rval = SDP8( 0., tle, sat_params, state_vect, state_vect + 3);\n         break;\n      default:\n         printf( \"??? ephem = %d\\n\", ephem);\n         rval = -99;\n         break;\n      }\n// if( rval)\n//    printf( \"??? rval = %d; ecc = %.6lf\\n\", rval, tle->eo);\n   return( rval);\n}\n\n#define SIMPLEX_POINT struct simplex_point\n\nSIMPLEX_POINT\n   {\n   double state_vect[6];\n   double error;\n   };\n\nstatic double total_vector_diff( const double *vect1, const double *vect2)\n{\n   int i;\n   double rval = 0.;\n\n   for( i = 0; i < 6; i++)\n      {\n      double delta = vect1[i] - vect2[i];\n      if( i >= 3)\n         delta *= 1000.;\n      rval += delta * delta;\n      }\n   return( rval);\n}\n\nstatic double compute_simplex_point_error( const double *state_vect, tle_t *tle,\n            const double *start, const int ephem)\n{\n   double rval = 0., state_out[6];\n   int compute_rval, vect_to_tle_rval;\n\n   vect_to_tle_rval = vector_to_tle( tle, state_vect);\n   if( vect_to_tle_rval == -1)\n      return( 1.e+37);\n   compute_rval = compute_new_state_vect( tle, state_out, ephem);\n   if( compute_rval == SXPX_ERR_NEARLY_PARABOLIC\n         || compute_rval == SXPX_ERR_NEGATIVE_MAJOR_AXIS\n         || compute_rval == SXPX_ERR_NEGATIVE_XN\n         || vect_to_tle_rval == -1)\n      rval = 1.e+37;       /* invalid vector */\n   else\n      rval = total_vector_diff( state_out, start);\n   return( rval);\n}\n\nstatic double try_simplex( SIMPLEX_POINT *simp, const double factor,\n               tle_t *tle, const double *start, const int ephem)\n{\n   SIMPLEX_POINT new_point;\n   int i, j;\n\n   for( i = 0; i < 6; i++)\n      {\n      new_point.state_vect[i] = factor * simp->state_vect[i];\n      for( j = 1; j < 7; j++)\n         new_point.state_vect[i] += (1. - factor) * simp[j].state_vect[i] / 6.;\n      }\n   new_point.error = compute_simplex_point_error( new_point.state_vect, tle,\n                                           start, ephem);\n   if( new_point.error <= simp->error)\n      *simp = new_point;\n   return( new_point.error);\n}\n\nstatic void sort_simplexes( SIMPLEX_POINT *simp)\n{\n   int i;\n\n   for( i = 0; i < 7; i++)          /* sort simplex points by error */\n      if( simp[i].error < simp[i + 1].error)   /* highest to lowest */\n         {\n         SIMPLEX_POINT temp_elem = simp[i];\n\n         simp[i] = simp[i + 1];\n         simp[i + 1] = temp_elem;\n         if( i)\n            i -= 2;\n         }\n}\n\ndouble dist_offset = 10000., vel_offset = 10.;\n\nstatic void create_randomized_simplex( SIMPLEX_POINT *simp, const double *start_vect)\n{\n   int i;\n\n   for( i = 0; i < 6; i++)\n      {\n      const double zval = (double)rand( ) / (double)RAND_MAX - .5;\n      simp->state_vect[i] = start_vect[i]\n                        + zval * (i < 3 ? dist_offset : vel_offset);\n      }\n}\n\nstatic int initialize_simplexes( SIMPLEX_POINT *simp, const double *state_vect,\n                                   const double *start_vect, const int ephem)\n{\n   int i, rval = 0;\n\n   memcpy( simp[6].state_vect, start_vect, 6 * sizeof( double));\n   assert( start_vect[0] && start_vect[1] && start_vect[2]);\n   for( i = 0; i < 7 && !rval; i++)\n      {\n      tle_t tle;\n      int iter = 0;\n\n      set_tle_defaults( &tle);\n      if( i != 6)\n         create_randomized_simplex( simp + i, start_vect);\n      while ( (simp[i].error = compute_simplex_point_error( simp[i].state_vect,\n                          &tle, state_vect, ephem)) > 1e+36 && iter++ < 1000)\n         create_randomized_simplex( simp, start_vect);\n      if( iter >= 1000)\n         rval = -1;\n      }\n   return( rval);\n}\n\nstatic int find_tle_via_simplex_method( tle_t *tle, const double *state_vect,\n                     const double *start_vect, const int ephem)\n{\n   SIMPLEX_POINT simp[7];\n   double best_rval_found = 1e+39, best_vect[6];\n   int i, j, soln_found = 0, n_iterations = 0;\n   int n_consecutive_contractions = 0;\n   const int max_iterations = 43000;\n\n   if( verbose)\n      show_results( \"Setting up:\", NULL, start_vect);\n   srand( 1);\n   if( initialize_simplexes( simp, state_vect, start_vect, ephem))\n      return( 0);       /* no solution found */\n   while( !soln_found && n_iterations++ < max_iterations)\n      {\n      double ytry;\n\n      sort_simplexes( simp);\n      ytry = try_simplex( simp, -1., tle, state_vect, ephem);\n      if( ytry <= simp[6].error)\n         {\n         if( verbose)\n            {\n            char buff[200];\n            printf( \"New record low: %f\\n\", ytry);\n\n            write_elements_in_tle_format( buff, tle);\n            printf( \"%s\", buff);\n            }\n         try_simplex( simp, 2., tle, state_vect, ephem);\n         if( ytry < 1e-13)\n            soln_found = true;\n         if( ytry < best_rval_found)\n            {\n            best_rval_found = ytry;\n            memcpy( best_vect, simp[0].state_vect, 6 * sizeof( double));\n            }\n         n_consecutive_contractions = 0;\n         }\n      else if( ytry > simp[1].error)\n         {\n         double ysave = simp[0].error;\n\n         ytry = try_simplex( simp, .5, tle, state_vect, ephem);\n         if( ytry > ysave)       /* still no success;  try contracting */\n            {                    /* around lowest point: */\n//          printf( \"Contracting around best point\\n\");\n            for( i = 0; i < 6; i++)\n               {\n               for( j = 0; j < 6; j++)\n                  simp[i].state_vect[j] =\n                           (simp[i].state_vect[j] + simp[6].state_vect[j]) / 2.;\n               simp[i].error = compute_simplex_point_error( simp[i].state_vect, tle,\n                                                               state_vect, ephem);\n               }\n            n_consecutive_contractions++;\n            if( n_consecutive_contractions == 30)\n               initialize_simplexes( simp, state_vect, best_vect, ephem);\n            }\n         else\n            n_consecutive_contractions = 0;\n         }\n      if( n_iterations % 200 == 199)\n         initialize_simplexes( simp, state_vect, best_vect, ephem);\n      }\n   sort_simplexes( simp);\n   if( verbose)\n      printf( \"End err: %f\\n\", simp[6].error);\n   vector_to_tle( tle, best_vect);\n// vector_to_tle( tle, simp[6].state_vect);\n   return( soln_found);\n}\n\nint compute_tle_from_state_vector( tle_t *tle, const double *state_vect, const int ephem,\n                        double *trial_state)\n{\n   int n_failed_steps = 0, i;\n   double state_out[6], best_vect[6], curr_err;\n   const double thresh = 1e-12;\n\n   memcpy( trial_state, state_vect, 6 * sizeof( double));\n   if( vector_to_tle( tle, state_vect))\n      {\n      printf( \"Immediate failure\\n\");\n      return( -1);\n      }\n   memcpy( best_vect, state_vect, 6 * sizeof( double));\n   compute_new_state_vect( tle, state_out, ephem);\n   for( i = 0; i < 6; i++)\n      trial_state[i] += state_vect[i] - state_out[i];\n   curr_err = total_vector_diff( state_out, state_vect);\n   if( verbose)\n      show_results( \"Initial guess\", tle, state_out);\n   if( curr_err < thresh)\n      printf( \"Got it right away\\n\");\n   while( curr_err > thresh && n_failed_steps < 20)\n      {\n      double new_err = 0.;\n      tle_t new_tle = *tle;\n\n      if( vector_to_tle( &new_tle, trial_state))\n         {\n         memcpy( trial_state, best_vect, 6 * sizeof( double));\n         show_results( \"Simple failure:\", tle, trial_state);\n         return( -1);\n         }\n      compute_new_state_vect( &new_tle, state_out, ephem);\n      new_err = total_vector_diff( state_out, state_vect);\n      if( new_err > curr_err * .9)\n          n_failed_steps++;      /* slow or no convergence */\n      if( new_err < curr_err)\n         {\n         curr_err = new_err;\n         *tle = new_tle;\n         memcpy( best_vect, trial_state, 6 * sizeof( double));\n         if( verbose)\n            {\n            printf( \"New record %f\\n\", curr_err);\n            show_results( NULL, tle, state_out);\n            }\n         }\n      for( i = 0; i < 6; i++)\n          trial_state[i] += state_vect[i] - state_out[i];\n      }\n   memcpy( trial_state, best_vect, 6 * sizeof( double));\n   return( curr_err > thresh);\n}\n\n/* Main program */\nint main( const int argc, const char **argv)\n{\n   const char *tle_filename = ((argc == 1) ? \"test.tle\" : argv[1]);\n   FILE *ifile = fopen( tle_filename, \"rb\");\n   tle_t tle; /* Pointer to two-line elements set for satellite */\n   char line1[100], line2[100];\n   int ephem = 1;       /* default to SGP4 */\n   int i;               /* Index for loops etc */\n   int n_failures = 0, n_simple = 0, n_simplex = 0;\n   bool failures_only = false;\n\n   for( i = 2; i < argc; i++)\n      if( argv[i][0] == '-')\n         switch( argv[i][1])\n            {\n            case 'f':\n               failures_only = true;\n               break;\n            case 'v':\n               verbose = 1;\n               break;\n            case 'd':\n               dist_offset = atof( argv[i] + 2);\n               break;\n            case 's':\n               vel_offset = atof( argv[i] + 2);\n               break;\n            default:\n               printf( \"Option '%s' unrecognized\\n\", argv[i]);\n               break;\n            }\n   if( !ifile)\n      {\n      printf( \"Couldn't open input TLE file %s\\n\", tle_filename);\n      exit( -1);\n      }\n   *line1 = '\\0';\n   while( fgets( line2, sizeof( line2), ifile))\n      {\n      int got_data = 0;\n      double state_vect[6];\n\n      set_tle_defaults( &tle);\n      if( strlen( line2) > 110 && line2[7] == '.' && line2[18] == '.'\n                     && line2[0] == '2' && line2[1] == '4')\n         {\n         got_data = 3;           /* Find_Orb state vector ephemeris */\n         tle.epoch = atof( line2);\n         sscanf( line2 + 13, \"%lf %lf %lf %lf %lf %lf\",\n                    state_vect + 0, state_vect + 1, state_vect + 2,\n                    state_vect + 3, state_vect + 4, state_vect + 5);\n         }\n      else if( strlen( line1) > 55 && !memcmp( line1 + 50, \" (TDB)\", 6))\n         {                                  /* JPL Horizons vector */\n         const double obliq_2000 = 23.4392911 * PI / 180.;\n\n         tle.epoch = atof( line1);          /* get JD epoch from header... */\n         strcpy( line1, line2);\n         if( fgets( line2, sizeof( line2), ifile))\n            got_data = 1;\n         sscanf( line1, \"%lf %lf %lf\",\n                    state_vect + 0, state_vect + 1, state_vect + 2);\n         sscanf( line2, \"%lf %lf %lf\",\n                    state_vect + 3, state_vect + 4, state_vect + 5);\n                      /* Cvt ecliptic to equatorial 2000: */\n         rotate_vector( state_vect    , obliq_2000, 0);\n         rotate_vector( state_vect + 3, obliq_2000, 0);\n         }\n      else if( parse_elements( line1, line2, &tle) >= 0)\n         got_data = 2;\n\n      if( got_data == 1 || got_data == 3)\n         tle.epoch -= 68.00 / 86400.;       /* rough convert TDT to UTC */\n\n      if( got_data)     /* hey! we got a TLE! */\n         {\n         double sat_params[N_SAT_PARAMS],  trial_state[6];\n         int simple_rval;\n         bool failed = false;\n         tle_t new_tle;\n\n         if( got_data == 1 || got_data == 3)\n            {\n            ephem = 3;        /* Use SDP4 for JPL Horizons vectors */\n            for( i = 0; i < 6 && fabs( state_vect[i]) < 1.; i++)\n               ;\n            if( i == 6)   /* all small quantities,  must be in AU & AU/day : */\n               {\n               for( i = 0; i < 6; i++)\n                  state_vect[i] *= AU_IN_KM;\n               for( i = 3; i < 6; i++)\n                  state_vect[i] /= seconds_per_day;\n               }\n            for( i = 3; i < 6; i++)    /* cvt km/sec to km/min */\n               state_vect[i] *= seconds_per_minute;\n            if( !failures_only)\n               show_results( \"Before:\", NULL, state_vect);\n            }\n         else\n            {\n            int is_deep = select_ephemeris( &tle);\n\n            if( is_deep && (ephem == 1 || ephem == 2))\n               ephem += 2;    /* switch to an SDx */\n            if( !is_deep && (ephem == 3 || ephem == 4))\n               ephem -= 2;    /* switch to an SGx */\n\n            /* Calling of NORAD routines */\n            /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8)   */\n            /* will be called in turn with the appropriate TLE set */\n            switch( ephem)\n               {\n               case 0:\n                  SGP_init( sat_params, &tle);\n                  SGP( 0., &tle, sat_params, state_vect, state_vect + 3);\n                  break;\n               case 1:\n                  SGP4_init( sat_params, &tle);\n                  SGP4( 0., &tle, sat_params, state_vect, state_vect + 3);\n                  break;\n               case 2:\n                  SGP8_init( sat_params, &tle);\n                  SGP8( 0., &tle, sat_params, state_vect, state_vect + 3);\n                  break;\n               case 3:\n                  SDP4_init( sat_params, &tle);\n                  SDP4( 0., &tle, sat_params, state_vect, state_vect + 3);\n                  break;\n               case 4:\n                  SDP8_init( sat_params, &tle);\n                  SDP8( 0., &tle, sat_params, state_vect, state_vect + 3);\n                  break;\n               }\n            if( !failures_only)\n               show_results( \"Before:\", &tle, state_vect);\n            }\n\n         new_tle = tle;\n         simple_rval = compute_tle_from_state_vector( &new_tle, state_vect, ephem, trial_state);\n         if( simple_rval)\n            {\n            n_simplex++;\n            find_tle_via_simplex_method( &new_tle, state_vect, trial_state, ephem);\n            }\n         else\n            n_simple++;\n\n         compute_new_state_vect( &new_tle, trial_state, ephem);\n         for( i = 0; i < 6; i++)\n            {\n            trial_state[i] -= state_vect[i];\n            if( fabs( trial_state[i]) > 1e-6)\n               failed = true;\n            }\n         if( failed && failures_only)\n            show_results( \"Before:\", &tle, state_vect);\n         if( failed || !failures_only)\n            show_results( (simple_rval ? \"Simplex result:\" : \"Simplest method:\"),\n                                &new_tle, trial_state);\n         if( failed)\n            n_failures++;\n         }\n      strcpy( line1, line2);\n      }\n   fclose( ifile);\n   printf( \"%d solved with simple method; %d with simplex\\n\", n_simple, n_simplex);\n   if( n_failures)\n      printf( \"%d failures\\n\", n_failures);\n   return(0);\n} /* End of main() */\n\n\n"
  },
  {
    "path": "fake_ast.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\n   This program will generate simulated geocentric observations\nfor a given object from a TLE.  In theory,  one can then fit these\npseudo-observations to a higher-quality physical model to get a\nconsiderably more accurate ephemeris for the object.  */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"norad.h\"\n#include \"observe.h\"\n\n#define PI 3.1415926535897932384626433832795028841971693993751058209749445923\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile = fopen( argv[1], \"rb\");\n   char line1[100], line2[100];\n   const char *intl_id = NULL;\n   double step_size = .1;\n   int i, n_steps = 100;\n   bool show_vectors = false;\n\n   if( !ifile)\n      {\n      printf( \"Couldn't open input file\\n\");\n      exit( -1);\n      }\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-')\n         switch( argv[i][1])\n            {\n            case 'i':\n               intl_id = argv[i] + 2;\n               break;\n            case 'n':\n               n_steps = atoi( argv[i] + 2);\n               break;\n            case 's':\n               step_size = atof( argv[i] + 2);\n               break;\n            case 'v':\n               show_vectors = true;\n               break;\n            default:\n               printf( \"Unrecognized option '%s'\\n\", argv[i]);\n               break;\n            }\n   *line1 = '\\0';\n   sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1);\n   while( fgets( line2, sizeof( line2), ifile))\n      {\n      tle_t tle; /* Pointer to two-line elements set for satellite */\n      int err_val;\n\n      if( (!intl_id || !memcmp( intl_id, line1 + 9, 6))\n                && (err_val = parse_elements( line1, line2, &tle)) >= 0)\n         {                  /* hey! we got a TLE! */\n         int is_deep = select_ephemeris( &tle);\n         double sat_params[N_SAT_PARAMS], observer_loc[3];\n         double prev_pos[3];\n\n         if( err_val)\n            printf( \"WARNING: TLE parsing error %d\\n\", err_val);\n         for( i = 0; i < 3; i++)\n            observer_loc[i] = 0.;\n         if( is_deep)\n            SDP4_init( sat_params, &tle);\n         else\n            SGP4_init( sat_params, &tle);\n         for( i = 0; i < n_steps; i++)\n            {\n            double pos[3]; /* Satellite position vector */\n            double t_since = (double)( i - n_steps / 2) * step_size;\n            double jd = tle.epoch + t_since;\n\n            t_since *= 1440.;\n            if( is_deep)\n               err_val = SDP4( t_since, &tle, sat_params, pos, NULL);\n            else\n               err_val = SGP4( t_since, &tle, sat_params, pos, NULL);\n            if( err_val)\n               printf( \"Ephemeris error %d\\n\", err_val);\n            if( show_vectors)\n               {\n               if( i)\n                  printf( \"%14.6f %14.6f %14.6f - \", pos[0] - prev_pos[0],\n                                                 pos[1] - prev_pos[1],\n                                                 pos[2] - prev_pos[2]);\n               printf( \"%14.6f %14.6f %14.6f\\n\", pos[0], pos[1], pos[2]);\n               memcpy( prev_pos, pos, 3 * sizeof( double));\n               }\n            else\n               {\n               double ra, dec, dist_to_satellite;\n\n               get_satellite_ra_dec_delta( observer_loc, pos,\n                                 &ra, &dec, &dist_to_satellite);\n               epoch_of_date_to_j2000( jd, &ra, &dec);\n               printf( \"%-14sC%13.5f    %08.4f    %+08.4f\",\n                     intl_id, jd, ra * 180. / PI, dec * 180. / PI);\n               printf( \"                    TLEs 500\\n\");\n               }\n            }\n         }\n      strcpy( line1, line2);\n      }\n   fclose( ifile);\n   return( 0);\n} /* End of main() */\n\n"
  },
  {
    "path": "fix_tles.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\nfix_tles : change TLE data and replace with correct checksums\n\n   If one alters a TLE,  one will normally cause a change in\nthe checksum data at the end of the line.  This program reads\nin successive lines from an input file;  if a line and the\npreceding line make a TLE,  the checksum is computed for\nboth lines and is suitably reset.\n\n   I've had instances where I realized that either the COSPAR\nor NORAD designation was incorrectly set,  and added options\nthat let you specify which designation should be used for all\nTLEs in the output.  (This is specific to my use case : I usually\ncompute many TLEs for a particular object,  which each TLE\nbeing fitted to give good state vectors over one day.)\n\n   At some point,  I may need to similarly batch-correct bulletin\nnumbers or something of that ilk,  but at present,  only the\ndesignations (and checksums) can be batch-corrected with this\nprogram.                                  */\n\n#include <string.h>\n#include <assert.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include \"norad.h\"\n\n/* After being modified,  the checksum byte in TLEs must be recomputed : */\n\nstatic void set_checksum( char *line)\n{\n   const int csum =  tle_checksum( line);\n\n   assert( csum >= 0);\n   line[68] += csum;\n   if( line[68] > '9')\n      line[68] -= 10;\n}\n\nstatic void usage( void)\n{\n   fprintf( stderr,\n       \"'fix_tles' reads in TLEs and outputs TLEs with corrected checksums.\\n\"\n       \"Options are :\\n\"\n       \"   -i YYNNNA     Replace COSPAR designation\\n\"\n       \"   -n NNNNN      Replace five-digit NORAD designation\\n\"\n       \"   -f filename   Specify input file (default = stdin)\\n\"\n       \"   -o filename   Specify output file (default = stdout)\\n\" );\n   exit( -1);\n}\n\nint main( const int argc, const char **argv)\n{\n   int i, line_no = 0;\n   const char *norad_desig = NULL;\n   char intl_desig[10];\n   FILE *ifile = stdin, *ofile = stdout;\n   char line1[200], line2[200];\n\n   *intl_desig = '\\0';\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-')\n         {\n         const char *arg = argv[i] + 2;\n\n         if( !*arg && i < argc - 1)\n            arg = argv[i + 1];\n         switch( argv[i][1])\n            {\n            case 'i':\n               assert( strlen( arg) < 9);\n               snprintf( intl_desig, sizeof( intl_desig), \"%-9s\", arg);\n               break;\n            case 'n':\n               norad_desig = arg;\n               break;\n            case 'o':\n               ofile = fopen( arg, \"wb\");\n               if( !ofile)\n                  {\n                  fprintf( stderr, \"'%s' not opened\\n\", arg);\n                  usage( );\n                  }\n               break;\n            case 'f':\n               ifile = fopen( arg, \"rb\");\n               if( !ifile)\n                  {\n                  fprintf( stderr, \"'%s' not opened\\n\", arg);\n                  usage( );\n                  }\n               break;\n            default:\n               fprintf( stderr, \"Unrecognized option '%s'\\n\", argv[i]);\n               usage( );\n               break;\n            }\n         }\n   *line1 = '\\0';\n   while( fgets( line2, sizeof( line2), ifile))\n      {\n      tle_t unused_tle;\n\n      if( parse_elements( line1, line2, &unused_tle) >= 0)\n         {\n         if( norad_desig)\n            {\n            memcpy( line1 + 2, norad_desig, 5);\n            memcpy( line2 + 2, norad_desig, 5);\n            }\n         if( *intl_desig)\n            memcpy( line1 + 9, intl_desig, 8);\n         set_checksum( line1);\n         set_checksum( line2);\n         }\n      if( line_no)\n         fputs( line1, ofile);\n      line_no++;\n      strcpy( line1, line2);\n      }\n   if( line_no)\n      fputs( line1, ofile);\n   return( 0);\n}\n"
  },
  {
    "path": "get_el.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <string.h>\n#include <stdint.h>\n#include <assert.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include \"norad.h\"\n\n#define PI 3.141592653589793238462643383279502884197\n#define TWOPI (2. * PI)\n#define MINUTES_PER_DAY 1440.\n#define MINUTES_PER_DAY_SQUARED (MINUTES_PER_DAY * MINUTES_PER_DAY)\n#define MINUTES_PER_DAY_CUBED (MINUTES_PER_DAY * MINUTES_PER_DAY_SQUARED)\n#define AE 1.0\n                             /* distance units, earth radii */\n\n/* TLEs have four angles on line 2,  given in the form DDD.DDDD.  This\ncan be parsed more quickly as an integer,  then cast to double and\nconverted to radians,  all in one step.    */\n\nstatic int get_angle( const char *buff)\n{\n   int rval = 0;\n\n   while( *buff == ' ')\n      buff++;\n   while( *buff != ' ')\n      {\n      if( *buff != '.')\n         rval = rval * 10 + (int)( *buff - '0');\n      buff++;\n      }\n   return( rval);\n}\n\n/* Converts the quasi scientific notation of the \"Motion Dot Dot/6\" or\n\"BSTAR\" field to double.  The input will always be of the form\n\nsdddddSe\n\n   ....where s is blank or + or -;  ddddd is a five-digit mantissa;\nS is + or - or blank;  and e is a single-digit exponent.  A decimal\npoint is assumed before the five-digit mantissa.  */\n\nstatic double sci( const char *string)\n{\n   double rval = 0.;\n\n   if( string[1] != ' ')\n      {\n      const int ival = atoi( string);\n\n      if( ival)\n         {\n         rval = (double)ival * 1.e-5;\n         if( string[7] != '0')\n            {\n            int exponent = string[7] - '0';\n\n            if( string[6] == '-')\n               while( exponent--)\n                  rval *= .1;\n            else\n               while( exponent--)\n                  rval *= 10.;\n            }\n         }\n      }\n   return( rval);\n}\n\n\n/* Does a checksum modulo 10 on the given line.  Digits = their\nvalue, '-' = 1, all other chars = 0.  Returns 0 if ok, a negative\nvalue if it's definitely not a TLE line,  positive if it's all OK\nexcept the checksum.  This last was added because people sometimes\nwant to use TLEs without worrying about the checksum. */\n\nint DLL_FUNC tle_checksum( const char *buff)\n{\n   int rval = 0;\n   int count = 69;\n\n   if( (*buff != '1' && *buff != '2') || buff[1] != ' ')\n      return( -1);\n   while( --count)\n      {\n      if( *buff > '0' && *buff <= '9')\n         rval += *buff - '0';\n      else if( *buff == '-')\n         rval++;\n      if( *buff < ' ' || *buff > 'z')           /* invalid character */\n         return( -2);\n      buff++;\n      }\n   rval -= *buff++ - '0';\n   if( *buff > ' ')                 /* line unterminated */\n      rval = -3;\n   else\n      {\n      rval %= 10;\n      if( rval < 0)\n         rval += 10;\n      }\n   return( rval);\n}\n\nstatic inline int mutant_dehex( const char ichar)\n{\n   int rval;\n\n   if( ichar <= '9' && ichar >= '0')\n      rval = ichar - '0';\n   else if( ichar >= 'A' && ichar <= 'Z')\n      rval = ichar + 10 - 'A';\n   else\n      rval = -1;\n   return( rval);\n}\n\n/* The \"standard\" SDP4 model fails badly for very high-flying satellites\n(mostly,  but not always,  those with orbital periods of greater than\nabout a week).  Highly eccentric orbits are more likely to fail than\nnear-circular ones.  And of course,  hyperbolic orbits never work with\nSGP4/SDP4.\n\n   As a non-standard extension,  I'm simply storing state vectors for\nsuch orbits,  using the following somewhat odd scheme :\n\n1 40391U 15007B   15091.99922241 sxxxxxxxx syyyyyyyy szzzzzzzzH  9997\n2 49391 [valid range, accuracy]  saaaaaaaa sbbbbbbbb scccccccc    0 8\n\n   Epoch,  int'l & NORAD IDs are stored in the standard manner.  The\n'ephemeris type' is H (rather than the otherwise universal 0).  The\nxyz position and vx, vy, vz velocity are stored as 8-digit signed\nbase-36 integers,  hence a range of +/- 36^8 = about +/- 2.82x10^12.\n\n  x, y, z are in meters,  and hence cover a range +/- 18.9 AU.\nvx, vy, vz are in 10^-4 m/s,  range +/- 94% c.  The state vectors\nare in the geocentric ecliptic plane of date.  See 'sdp4.cpp' for\na discussion of how they're actually used.  */\n\nstatic double get_high_value( const char *iptr)\n{\n   int64_t rval = 0;\n\n   assert( *iptr == '+' || *iptr == '-');\n   if( *iptr == '+' || *iptr == '-')\n      {\n      int i, digit;\n\n      for( i = 1; i < 9; i++)\n         {\n         digit = mutant_dehex( iptr[i]);\n         assert( digit >= 0);\n         rval = rval * (int64_t)36 + (int64_t)digit;\n         }\n      if( *iptr == '-')\n         rval = -rval;\n      }\n   return( (double)rval);\n}\n\n/* Traditionally,  NORAD numbers were stored as five digits.  In 2020, new\ndetectors threatened to go past 100K objects;  the 'Alpha-5' scheme allows\nthe first byte to be replaced by an uppercase letter,  with I and O\nskipped.  That gets us to 339999 :\n\nhttps://www.space-track.org/documentation#tle-alpha5\n\n   Note that Alpha-5 is referred to as a \"stopgap\".  Near the bottom of\nthe above link,  \"space-track.org encourages users to switch to... XML,\nKVN,  or JSON\",  (partly) because these will handle nine-digit catalog\nnumbers.\n\n   To go beyond the Alpha-5 limit of 340000 possible numbers and store\nall nine-digit numbers in five bytes,  I have added options 3 and 4\nbelow.  To do so,  we need a 'base64'-like scheme,  using all ten\ndigits,  26 uppercase and 26 lowercase letters,  and + and /.\n\n   d = digit, L = uppercase letter,  x = any base64 character\n   X = non-digit base-64 character\n\n(1) ddddd = 'traditional' scheme provides 100000 combinations;\n         Numbers 0 to 99999\n\n(2) Ldddd = Alpha-5 scheme adds 240000\n         Numbers 100000 to 339999;     A0000 to Z9999\n\n(3) xxxxX = 64^4*54      = 905969664 more  (start of 'Super-5' range)\n         Numbers 340000 to 906309663;  0000A to -----\n\n(4) xxxXd = 64^3*54*10   = 141557760 more\n         Numbers 906309664 to 1047867423;  000A0 and up\n              (going slightly past the billion we actually need) */\n\nstatic int base64_to_int( const char c)\n{\n   int offset;\n\n   if( c >= 'A')\n      {\n      if( c <= 'Z')\n         offset = 'A' - 10;\n      else if( c >= 'a' && c <= 'z')\n         offset = 'a' - 10 - 26;\n      else\n         return( -1);\n      }\n   else\n      {\n      if( c >= '0' && c <= '9')\n         offset = '0';\n      else if( c == ' ')\n         return( 0);\n      else if( c == '+')\n         return( 62);\n      else if( c == '-')\n         return( 63);\n      else\n         return( -1);\n      }\n   return( c - offset);\n}\n\nstatic int get_norad_number( const char *buff)\n{\n   size_t i;\n   int digits[5], rval = 0;\n\n   for( i = 0; i < 5; i++)\n      {\n      digits[i] = base64_to_int( buff[i]);\n      if( digits[i] == -1)       /* not a valid number */\n         return( 0);\n      }\n   if( digits[4] > 9)      /* case (3): last char is uppercase */\n      rval = 340000 + (digits[4] - 10)\n            + 54 * (digits[3] + (digits[2] << 6) + (digits[1] << 12) + (digits[0] << 18));\n   else if( digits[3] > 9)    /* case (4) above */\n      rval = 340000 + 905969664 + digits[4] + (digits[3] - 10) * 10\n            + 540 * (digits[2] + (digits[1] << 6) + (digits[0] << 12));\n   else        /* last four digits are 0-9;  'standard' NORAD desig */\n      {\n      for( i = 1; i <= 4; i++)\n         assert( (buff[i] >= '0' && buff[i] <= '9') || buff[i] == ' ');\n      if( *buff > 'I')\n         {\n         digits[0]--;\n         if( *buff > 'O')\n            digits[0]--;\n         }\n      rval = digits[0] * 10000 + atoi( buff + 1);\n      }\n   return( rval);\n}\n\nstatic inline double get_eight_places( const char *ptr)\n{\n   return( (double)atoi( ptr) + (double)atoi(ptr + 4) * 1e-8);\n}\n\n/* Meteor 2-08                                                           */\n/* 1 13113U          88245.60005115 0.00000076           63463-4 0  5998 */\n/* 2 13113  82.5386 288.0994 0015973 147.1294 213.0868 13.83869004325321 */\n\n#define J2000 2451545.5\n#define J1900 (J2000 - 36525. - 1.)\n\n/* parse_elements returns:\n         0 if the elements are parsed without error;\n         1 if they're OK except the first line has a checksum error;\n         2 if they're OK except the second line has a checksum error;\n         3 if they're OK except both lines have checksum errors;\n         a negative value if the lines aren't at all parseable */\n\nint DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t *sat)\n{\n   int rval, checksum_problem = 0;\n\n   if( *line1 != '1' || *line2 != '2')\n      rval = -4;\n   else\n      {\n      rval = tle_checksum( line1);\n      if( rval > 0)\n         {\n         checksum_problem = 1;  /* there's a checksum problem,  but it's */\n         rval = 0;              /* not fatal; continue processing the TLE */\n         }\n      }\n\n   if( rval)\n      rval -= 100;\n   else\n      {\n      rval = tle_checksum( line2);\n      if( rval > 0)\n         {\n         checksum_problem |= 2;  /* there's a checksum problem,  but it's */\n         rval = 0;               /* not fatal; continue processing the TLE */\n         }\n      }\n\n   if( !rval)\n      {\n      char tbuff[13];\n      int year = line1[19] - '0';\n\n      if( line1[18] >= '0')\n         year += (line1[18] - '0') * 10;\n      if( year < 57)          /* cycle around Y2K */\n         year += 100;\n      sat->epoch = get_eight_places( line1 + 20) + J1900\n             + (double)( year * 365 + (year - 1) / 4);\n      sat->norad_number = get_norad_number( line1 + 2);\n      memcpy( tbuff, line1 + 64, 4);\n      tbuff[4] = '\\0';\n      sat->bulletin_number = atoi( tbuff);\n      sat->classification = line1[7];       /* almost always 'U' */\n      memcpy( sat->intl_desig, line1 + 9, 8);\n      if( !memcmp( sat->intl_desig, \"     \", 5))\n         {  /* usually 'analyst' object w/o international (COSPAR) desig; */\n         int i, n = sat->norad_number;      /* set launch 000,  year/part */\n                                            /* data mapped from NORAD #   */\n         for( i = 7; i > 4; i--, n /= 26)\n            sat->intl_desig[i] = 'A' + n % 26;\n         sat->intl_desig[2] = sat->intl_desig[3] = sat->intl_desig[4] = '0';\n         sat->intl_desig[1] = '0' + n % 10;\n         sat->intl_desig[0] = '0' + n / 10;\n         }\n      sat->intl_desig[8] = '\\0';\n      memcpy( tbuff, line2 + 63, 5);\n      tbuff[5] = '\\0';\n      sat->revolution_number = atoi( tbuff);\n      sat->ephemeris_type = line1[62];\n      if( sat->ephemeris_type == 'H')\n         {\n         size_t i;\n         double *state_vect = &sat->xincl;\n\n         for( i = 0; i < 3; i++)\n            {\n            state_vect[i]     = get_high_value( line1 + 33 + i * 10);\n            state_vect[i + 3] = get_high_value( line2 + 33 + i * 10) * 1e-4;\n            }\n         return( 0);\n         }\n\n      sat->xmo = (double)get_angle( line2 + 43) * (PI / 180e+4);\n      sat->xnodeo = (double)get_angle( line2 + 17) * (PI / 180e+4);\n      sat->omegao = (double)get_angle( line2 + 34) * (PI / 180e+4);\n      sat->xincl = (double)get_angle( line2 + 8) * (PI / 180e+4);\n      sat->eo = atoi( line2 + 26) * 1.e-7;\n\n      /* Make sure mean motion is null-terminated, since rev. no.\n          may immediately follow. */\n      memcpy( tbuff, line2 + 51, 12);\n      tbuff[12] = '\\0';\n            /* Input mean motion,  derivative of mean motion and second  */\n            /* deriv of mean motion,  are all in revolutions and days.   */\n            /* Convert them here to radians and minutes:                 */\n      sat->xno = get_eight_places( tbuff) * TWOPI / MINUTES_PER_DAY;\n      sat->xndt2o = (double)atoi( line1 + 35)\n                        * 1.e-8 * TWOPI / MINUTES_PER_DAY_SQUARED;\n      if( line1[33] == '-')\n         sat->xndt2o *= -1.;\n      sat->xndd6o = sci( line1 + 44) * TWOPI / MINUTES_PER_DAY_CUBED;\n\n      sat->bstar = sci( line1 + 53) * AE;\n      }\n   return( rval ? rval : checksum_problem);\n}\n"
  },
  {
    "path": "get_high.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/* Code to extract elements for high-flying artsats. Give */\n/* it the name of the input file of TLEs and a cutoff of  */\n/* the mean motion,  and only TLEs with a lower motion    */\n/* will be output.                                        */\n\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n#include <stdlib.h>\n#include \"norad.h\"\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile = fopen((argc > 1 ? argv[1] : \"all_tle.txt\"), \"rb\");\n   FILE *ofile;\n   char line0[200], line1[200], line2[200];\n   const double cutoff = (argc > 2 ? atof( argv[2]) : .6);\n   const time_t t0 = time( NULL);\n\n   if( !ifile)\n      perror( \"Input file not opened\");\n   ofile = (argc > 3 ? fopen( argv[3], \"a\") : stdout);\n   if( !ofile)\n      perror( \"Output file not opened\");\n   if( !ifile || !ofile)\n      return( -1);\n   *line0 = *line1 = '\\0';\n   fprintf( ofile, \"# Added %.24s UTC\\n\", asctime( gmtime( &t0)));\n   while( fgets( line2, sizeof( line2), ifile))\n      {\n      if( *line2 == '2' && *line1 == '1'\n               && !tle_checksum( line1) && !tle_checksum( line2)\n               && atof( line2 + 52) < cutoff)\n         fprintf( ofile, \"%s%s%s\", line0, line1, line2);\n      strcpy( line0, line1);\n      strcpy( line1, line2);\n      }\n   fclose( ifile);\n   return( 0);\n}\n"
  },
  {
    "path": "get_vect.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\n   This code can read a TLE and compute a geocentric state vector,\nformatted such that Find_Orb can then read it in.  I did this\npartly to test the hypothesis that if you compute a state vector\nfrom Space-Track TLEs at their epoch,  you get the \"actual\" motion.\nThat is to say,  you could numerically integrate it to get a better\nresult.  This turns out not to be the case.  Space-Track TLEs may\nbe a best-fit to a set of observations or (as with my own TLEs)\na best fit to a numerically integrated ephemeris,  but there\ndoesn't seem to be a way to improve them by doing a numerical\nintegration.\n\n   My second purpose was to be able to feed the state vector created\nby this program into Find_Orb as an initial orbit guess.  For that\npurpose,  it seems to work.  You see large residuals as a result\nof the difference between numerical integration and SGP4/SDP4.\nBut it gets you close enough that you can then do differential\ncorrections (least-squares fitting).         */\n\n#include <stdio.h>\n#include <string.h>\n#include <math.h>\n#include \"norad.h\"\n#include \"watdefs.h\"\n#include \"afuncs.h\"\n#include \"date.h\"\n\n#define PI \\\n   3.1415926535897932384626433832795028841971693993751058209749445923\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile;\n   const char *filename = \"all_tle.txt\";\n   char line0[100], line1[100], line2[100];\n   int i;\n   const char *norad = NULL, *intl = NULL;\n   double jd = 0.;\n   int is_j2000 = 1, is_equatorial = 1;\n\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-')\n         {\n         const char *arg = (i < argc - 1 && !argv[i][2]\n                                          ? argv[i + 1] : argv[i] + 2);\n\n         switch( argv[i][1])\n            {\n            case 'n':\n               norad = arg;\n               printf( \"Looking for NORAD %s\\n\", norad);\n               break;\n            case 'i':\n               intl = arg;\n               printf( \"Looking for international ID %s\\n\", intl);\n               break;\n            case 't':\n               jd = get_time_from_string( 0., arg, FULL_CTIME_YMD, NULL);\n               break;\n            case 'd':\n               is_j2000 = 0;\n               break;\n            default:\n               printf( \"'%s': unrecognized option\\n\", argv[i]);\n               return( -1);\n               break;\n            }\n         }\n   if( argc > 1 && argv[1][0] != '-')\n      filename = argv[1];\n   ifile = fopen( filename, \"rb\");\n   if( !ifile)\n      {\n      fprintf( stderr, \"Couldn't open '%s': \", filename);\n      perror( \"\");\n      return( -1);\n      }\n   *line0 = *line1 = '\\0';\n   while( fgets( line2, sizeof( line2), ifile))\n      {\n      if( *line1 == '1' && (!norad || !memcmp( line1 + 2, norad, 5))\n                        && (!intl || !memcmp( line1 + 9, intl, strlen( intl)))\n                   && *line2 == '2')\n         {\n         tle_t tle;\n         const int err_code = parse_elements( line1, line2, &tle);\n\n         if( err_code >= 0)\n            {\n            const int is_deep = select_ephemeris( &tle);\n            double state[6], state_j2000[6], precess_matrix[9];\n            double params[N_SAT_PARAMS], t_since;\n            const double epoch_tdt =\n                        tle.epoch + td_minus_utc( tle.epoch) / seconds_per_day;\n            const double J2000 = 2451545.;\n            double *state_to_show;\n\n            if( !jd)\n               jd = epoch_tdt;\n            t_since = (jd - epoch_tdt) * minutes_per_day;\n            if( is_deep)\n               {\n               SDP4_init( params, &tle);\n               SDP4( t_since, &tle, params, state, state + 3);\n               }\n            else\n               {\n               SGP4_init( params, &tle);\n               SGP4( t_since, &tle, params, state, state + 3);\n               }\n            if( strlen( line0) < 60)\n               printf( \"%s\", line0);\n            setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.);\n            precess_vector( precess_matrix, state, state_j2000);\n            precess_vector( precess_matrix, state + 3, state_j2000 + 3);\n            state_to_show = (is_j2000 ? state_j2000 : state);\n            printf( \" %.6f %.6s\\n\", jd, line1 + 9);\n            printf( \" %.5f %.5f %.5f 0408   # Ctr 3 km sec %s %s\\n\",\n                           state_to_show[0], state_to_show[1], state_to_show[2],\n                           is_equatorial ? \"eq\" : \"ecl\",\n                           is_j2000 ? \"\" : \"of_date\");\n            printf( \" %.5f %.5f %.5f 0 0 0\\n\",\n                           state_to_show[3] / seconds_per_minute,\n                           state_to_show[4] / seconds_per_minute,\n                           state_to_show[5] / seconds_per_minute);\n            }\n         }\n      strcpy( line0, line1);\n      strcpy( line1, line2);\n      }\n   fclose( ifile);\n   return( 0);\n}\n"
  },
  {
    "path": "line2.cpp",
    "content": "/* See LICENSE.   NOTE that this has been obsoleted by the 'add_off.c'\nprogram in the 'lunar' repository (q.v.).  The only use this code would\nhave would be for a spacecraft getting astrometric data for which we\ndon't have Horizons data.  I don't expect that to happen.  With that\ndisclaimer :\n\n   The Minor Planet Center accepts astrometry from spacecraft using\na modification of their usual 80-column \"punched-card\" format.  A\nsecond line is used to tell you where the spacecraft was,  relative\nto the geocenter.\n\nhttps://minorplanetcenter.net/iau/info/SatelliteObs.html\n\n   This code can reset those positions for Earth-orbiting spacecraft using\nTLEs ('two-line elements';  https://www.projectpluto.com/tle_info.htm.)\nThis helps when accurate positions are not provided or completely trusted\nor appear to be bad.\n\n   This code will read the 80-column astrometry and,  when it finds a\nspacecraft position report (the \"second line\"),  look through the TLE\nfile for a matching TLE.  If it finds one,  it'll compute the spacecraft\nlocation for that time and modify the position accordingly.\n\n   This was written specifically to handle a problem with a satellite in\nLEO.  TLEs don't exist for heliocentric objects,  but I may revise this\ncode eventually to add positions for them (this could be done by,  for\nexample,  requesting them from JPL Horizons).  TLEs exist for TESS,\ncomputed by me,  but some work would be required to make those actually\nfunction properly (the numbers are larger and the format is somewhat\ndifferent as a result).  So this really works,  at present,  only for\n(C51) WISE, (C52) Swift,  and (C53) NEOSSat.             */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#include <errno.h>\n#include <math.h>\n#include \"norad.h\"\n#include \"watdefs.h\"\n#include \"afuncs.h\"\n#include \"mpc_func.h\"\n#include \"stringex.h\"\n\n#define PI \\\n   3.1415926535897932384626433832795028841971693993751058209749445923\n\nint find_tle( tle_t *tle, const char *filename, const int norad_no)\n{\n   FILE *ifile = fopen( filename, \"rb\");\n   char line0[100], line1[100], line2[100];\n   int rval = -1;\n\n   if( !ifile)\n      {\n      fprintf( stderr, \"Couldn't open TLE file '%s'\\n\", filename);\n      exit( -1);\n      }\n   *line0 = *line1 = '\\0';\n   while( rval && fgets( line2, sizeof( line2), ifile))\n      {\n      if( *line1 == '1' && *line2 == '2' && atoi( line1 + 2) == norad_no)\n         if( parse_elements( line1, line2, tle) >= 0)\n            rval = 0;\n      strlcpy_error( line0, line1);\n      strlcpy_error( line1, line2);\n      }\n   fclose( ifile);\n   if( rval)\n      {\n      fprintf( stderr, \"Couldn't find TLEs for %5d in TLE file '%s'\\n\",\n                           norad_no, filename);\n      exit( -1);\n      }\n   return( rval);\n}\n\n/*\nC49 = 29510 = 2006-047A = STEREO-A\nC50 = 29511 = 2006-047B = STEREO-B\nC51 = 36119 = 2009-071A = WISE   *\nC52 = 28485 = 2004-047A = Swift   *\nC53 = 39089 = 2013-009D = NEOSSat  *\nC54 = 28928 = 2006-001A = New Horizons\nC55 = 34380 = 2009-011A = Kepler\nC56 = 41043 = 2015-070A = LISA-Pathfinder\nC57 = 43435 = 2018-038A = TESS      *\n   Note that only the asterisked objects are actually in earth orbit\nand therefore have TLEs.  But I may try to figure out some way to add\nin positions for the heliocentric spacecraft later.  */\n\nstatic int mpc_code_to_norad_number( const char *mpc_code)\n{\n   const char *codes = \"C49 C50 C51 C52 C53 C54 C55 C56 C57 \";\n   const int norad_numbers[] = { 29510, 29511, 36119, 28485, 39089,\n                                        28928, 34380, 41043, 43435 };\n   int i;\n\n   for( i = 0; codes[i * 4]; i++)\n      if( !memcmp( codes + i * 4, mpc_code, 3))\n         return( norad_numbers[i]);\n   assert( 1);          /* not supposed to ever happen,  unless a new */\n   return( 0);          /* satellite has been added */\n}\n\nint main( const int argc, const char **argv)\n{\n   const char *tle_filename = (argc > 2 ? argv[2] : \"all_tle.txt\");\n   const char *astrometry_filename = argv[1];\n   FILE *ifile = (argc > 1 ? fopen( astrometry_filename, \"rb\") : NULL);\n   int is_deep = 0, curr_norad = 0;\n   char buff[200];\n   double params[N_SAT_PARAMS];\n   const double J2000 = 2451545.;\n   tle_t tle;\n\n   if( argc > 1 && !ifile)\n      fprintf( stderr, \"'%s' not found : %s\\n\", astrometry_filename, strerror( errno));\n   if( !ifile)\n      {\n      fprintf( stderr, \"'line2' takes as a command-line argument the name of the input\\n\"\n                       \"astrometry file.  It sends astrometry with corrected/added\\n\"\n                       \"spacecraft locations to stdout.\\n\");\n      exit( -1);\n      }\n   while( fgets( buff, sizeof( buff), ifile))\n      {\n      double jd;\n\n      if( strlen( buff) > 80 && buff[14] == 's'\n            && (jd = extract_date_from_mpc_report( buff, NULL)) > 2400000.)\n         {\n         double state[6], state_j2000[6], precess_matrix[9];\n         double t_since;\n         const int norad_number = mpc_code_to_norad_number( buff + 77);\n         int i;\n\n         if( curr_norad != norad_number)\n            {\n            curr_norad = norad_number;\n            find_tle( &tle, tle_filename, norad_number);\n            is_deep = select_ephemeris( &tle);\n            if( is_deep)\n               SDP4_init( params, &tle);\n            else\n               SGP4_init( params, &tle);\n            }\n         t_since = (jd - tle.epoch) * minutes_per_day;\n         if( is_deep)\n            SDP4( t_since, &tle, params, state, state + 3);\n         else\n            SGP4( t_since, &tle, params, state, state + 3);\n         setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.);\n         precess_vector( precess_matrix, state, state_j2000);\n         precess_vector( precess_matrix, state + 3, state_j2000 + 3);\n         for( i = 0; i < 3; i++)\n            {\n            char *tptr = buff + 34 + i * 12;\n\n            snprintf_err( tptr, 12, \"%11.4f\", fabs( state_j2000[i]));\n            *tptr = (state_j2000[i] > 0. ? '+' : '-');\n            tptr[11] = ' ';\n            }\n         }\n      printf( \"%s\", buff);\n      }\n   fclose( ifile);\n   return( 0);\n}\n"
  },
  {
    "path": "makefile",
    "content": "# GNU MAKE Makefile for artsat code and utilities\n#\n# Usage: make [W64=Y] [W32=Y] [MSWIN=Y] [tgt]\n#\n#\twhere tgt can be any of:\n# [all|get_high|mergetle|obs_tes2|...]\n#\n# ...see below for complete list.  Note that you have to 'make tle_date' and/or\n# 'make tle_date.cgi' separately;  they have a dependency on the 'lunar'\n# library (also on my GitHub site).\n#\n#\t'W64'/'W32' = cross-compile for 64- or 32-bit Windows,  using MinGW,\n#     on a Linux box\n#\t'MSWIN' = compile for Windows,  using MinGW and PDCurses,  on a Windows machine\n#\t'CC=clang' or 'CC=g++-4.8' = use clang or older GCC\n#    (I've used gmake CLANG=Y on PC-BSD;  probably works on OS/X too)\n# None of these: compile using default g++ on Linux,  for Linux\n#\n\n# As CC is an implicit variable, a simple CC?=g++ doesn't work.\n# We have to use this trick from https://stackoverflow.com/a/42958970\nifeq ($(origin CC),default)\n\tCC=gcc\n\tCXX=g++\nendif\n\nifeq ($(shell uname -s),FreeBSD)\n\tCC=cc\n\tCXX=c++\nendif\n\nEXE=\nRM=rm -f\n\nLIB_DIR=$(INSTALL_DIR)/lib\n\nifdef W64\n\tCC=x86_64-w64-mingw32-gcc\n\tCXX=x86_64-w64-mingw32-g++\n\tEXE=.exe\n\tLIB_DIR=$(INSTALL_DIR)/win_lib\nendif\n\nifdef W32\n\tCC=i686-w64-mingw32-gcc\n\tCXX=i686-w64-mingw32-g++\n\tEXE=.exe\n\tLIB_DIR=$(INSTALL_DIR)/win_lib32\nendif\n\n# I'm using 'mkdir -p' to avoid error messages if the directory exists.\n# It may fail on very old systems,  and will probably fail on non-POSIX\n# systems.  If so,  change to '-mkdir' and ignore errors.\n\nifeq ($(EXE),.exe)\n\tMKDIR=-mkdir\nelse\n\tZLIB=-lz\n\tMKDIR=mkdir -p\nendif\n\n# You can have your include files in ~/include and libraries in\n# ~/lib,  in which case only the current user can use them;  or\n# (with root privileges) you can install them to /usr/local/include\n# and /usr/local/lib for all to enjoy.\n\nPREFIX?=~\nifdef GLOBAL\n\tINSTALL_DIR=/usr/local\nelse\n\tINSTALL_DIR=$(PREFIX)\nendif\n\nINCL=$(INSTALL_DIR)/include\n\nall: dropouts$(EXE) fake_ast$(EXE) fix_tles$(EXE) get_high$(EXE) \\\n\tline2$(EXE) mergetle$(EXE) obs_tes2$(EXE) obs_test$(EXE) \\\n\tout_comp$(EXE) sat_cgi$(EXE) sat_eph$(EXE) sat_id$(EXE) \\\n\tsat_id2$(EXE) sat_id3$(EXE) summarize$(EXE) \\\n\ttest_des$(EXE) test_out$(EXE) test_sat$(EXE) test2$(EXE) tle2mpc$(EXE)\n\nCFLAGS+=-Wextra -Wall -O3 -pedantic -Wshadow\n\nifdef UCHAR\n\tCFLAGS += -funsigned-char\nendif\n\nifdef DEBUG\n\tCFLAGS += -g\nendif\n\nifndef NO_ERRORS\n\tCFLAGS += -Werror\nendif\n\n\nclean:\n\t$(RM) *.o\n\t$(RM) dropouts$(EXE)\n\t$(RM) fake_ast$(EXE)\n\t$(RM) fix_tles$(EXE)\n\t$(RM) get_high$(EXE)\n\t$(RM) get_vect$(EXE)\n\t$(RM) libsatell.a\n\t$(RM) line2$(EXE)\n\t$(RM) mergetle$(EXE)\n\t$(RM) obs_tes2$(EXE)\n\t$(RM) obs_test$(EXE)\n\t$(RM) out_comp$(EXE)\n\t$(RM) sat_cgi$(EXE)\n\t$(RM) sat_eph$(EXE)\n\t$(RM) sat_id$(EXE)\n\t$(RM) sat_id2$(EXE)\n\t$(RM) sat_id3$(EXE)\n\t$(RM) summarize$(EXE)\n\t$(RM) test2$(EXE)\n\t$(RM) test_des$(EXE)\n\t$(RM) test_out$(EXE)\n\t$(RM) test_sat$(EXE)\n\t$(RM) tle2mpc$(EXE)\n\t$(RM) tle_date$(EXE)\n\t$(RM) tle_date.cgi\n\ninstall:\n\t$(MKDIR) $(LIB_DIR)\n\tcp libsatell.a $(LIB_DIR)\n\tcp norad.h     $(INSTALL_DIR)/include\n\t$(MKDIR) $(INSTALL_DIR)/bin\n\tcp sat_id$(EXE)  $(INSTALL_DIR)/bin\n\ninstall_lib:\n\t$(MKDIR) $(LIB_DIR)\n\tcp libsatell.a $(LIB_DIR)\n\tcp norad.h     $(INSTALL_DIR)/include\n\nuninstall:\n\trm $(INSTALL_DIR)/lib/libsatell.a\n\trm $(INSTALL_DIR)/include/norad.h\n\trm $(INSTALL_DIR)/bin/sat_id\n\nuninstall_lib:\n\trm $(INSTALL_DIR)/lib/libsatell.a\n\trm $(INSTALL_DIR)/include/norad.h\n\nOBJS= sgp.o sgp4.o sgp8.o sdp4.o sdp8.o deep.o basics.o get_el.o common.o tle_out.o\n\nget_high$(EXE):\t get_high.o get_el.o\n\t$(CC) $(CFLAGS) -o get_high$(EXE) get_high.o get_el.o\n\nmergetle$(EXE):\t mergetle.o\n\t$(CC) $(CFLAGS) -o mergetle$(EXE) mergetle.o -lm\n\ndropouts$(EXE):\t dropouts.o\n\t$(CC) $(CFLAGS) -o dropouts$(EXE) dropouts.o\n\nobs_tes2$(EXE):\t obs_tes2.o observe.o libsatell.a\n\t$(CC) $(CFLAGS) -o obs_tes2$(EXE) obs_tes2.o observe.o libsatell.a -lm\n\nobs_test$(EXE):\t obs_test.o observe.o libsatell.a\n\t$(CC) $(CFLAGS) -o obs_test$(EXE) obs_test.o observe.o libsatell.a -lm\n\nfake_ast$(EXE):\t fake_ast.o observe.o libsatell.a\n\t$(CC) $(CFLAGS) -o fake_ast$(EXE) fake_ast.o observe.o libsatell.a -lm\n\nfix_tles$(EXE):\t fix_tles.o libsatell.a\n\t$(CC) $(CFLAGS) -o fix_tles$(EXE) fix_tles.o libsatell.a -lm\n\nget_vect$(EXE):\t \tget_vect.cpp\tobserve.o libsatell.a\n\t$(CXX) $(CFLAGS) -o get_vect$(EXE) -I $(INCL) get_vect.cpp observe.o libsatell.a -lm -L $(LIB_DIR) -llunar\n\nline2$(EXE):\t \tline2.cpp libsatell.a\n\t$(CXX) $(CFLAGS) -o line2$(EXE) -I $(INCL) line2.cpp libsatell.a -lm -L $(LIB_DIR) -llunar\n\nout_comp$(EXE):\t out_comp.o\n\t$(CC) $(CFLAGS) -o out_comp$(EXE) out_comp.o -lm\n\nlibsatell.a: $(OBJS)\n\trm -f libsatell.a\n\tar rv libsatell.a $(OBJS)\n\nsat_eph$(EXE):\t \tsat_eph.c\tobserve.o libsatell.a\n\t$(CC) $(CFLAGS) -o sat_eph$(EXE) -I $(INCL) sat_eph.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)\n\nsat_cgi$(EXE):\t \tsat_eph.c\tobserve.o libsatell.a\n\t$(CC) $(CFLAGS) -o sat_cgi$(EXE) -I $(INCL) sat_eph.c observe.o -DON_LINE_VERSION libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)\n\nsat_id$(EXE):\t \tsat_id.cpp sat_util.o\tobserve.o libsatell.a\n\t$(CXX) $(CFLAGS) -o sat_id$(EXE) -I $(INCL) sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)\n\nsat_id2$(EXE):\t \tsat_id2.cpp sat_id.cpp sat_util.o observe.o libsatell.a\n\t$(CXX) $(CFLAGS) -o sat_id2$(EXE) -I $(INCL) -DON_LINE_VERSION sat_id2.cpp sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)\n\nsat_id3$(EXE):\t \tsat_id3.cpp sat_id.cpp sat_util.o observe.o libsatell.a\n\t$(CXX) $(CFLAGS) -o sat_id3$(EXE) -I $(INCL) -DON_LINE_VERSION sat_id3.cpp sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)\n\nsummarize$(EXE):\t \tsummarize.c\tobserve.o libsatell.a\n\t$(CC) $(CFLAGS) -o summarize$(EXE) -I $(INCL) summarize.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar\n\ntest2$(EXE):\t \ttest2.o sgp.o libsatell.a\n\t$(CC) $(CFLAGS) -o test2$(EXE) test2.o sgp.o libsatell.a -lm\n\ntle_date$(EXE):\t \ttle_date.o\n\t$(CC) $(CFLAGS) -o tle_date$(EXE) tle_date.o -L $(LIB_DIR) -llunar -lm\n\ntle_date.o: tle_date.c\n\t$(CC) $(CFLAGS) -o tle_date.o -c -I../include tle_date.c\n\ntle_date.cgi:\t \ttle_date.c\n\t$(CC) $(CFLAGS) -o tle_date.cgi  -I../include -DON_LINE_VERSION tle_date.c -L $(LIB_DIR) -llunar\n\ntle2mpc$(EXE):\t \ttle2mpc.cpp libsatell.a\n\t$(CXX) $(CFLAGS) -o tle2mpc$(EXE) -I $(INCL) tle2mpc.cpp libsatell.a -lm -L $(LIB_DIR) -llunar\n\ntest_des$(EXE):\t test_des.o libsatell.a\n\t$(CC) $(CFLAGS) -o test_des$(EXE) test_des.o libsatell.a -lm\n\ntest_out$(EXE):\t test_out.o tle_out.o get_el.o sgp4.o common.o\n\t$(CC) $(CFLAGS) -o test_out$(EXE) test_out.o tle_out.o get_el.o sgp4.o common.o -lm\n\ntest_sat$(EXE):\t test_sat.o libsatell.a\n\t$(CC) $(CFLAGS) -o test_sat$(EXE) test_sat.o libsatell.a -lm\n\n.cpp.o:\n\t$(CXX) $(CFLAGS) -c $<\n\n.c.o:\n\t$(CC) $(CFLAGS) -c $<\n"
  },
  {
    "path": "mergetle.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\nCode to read one or more files of TLEs,  remove duplicates,\nand sort them according to various possible criteria (eccentricity,\norbital period,  etc.)   */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n\n#define TLE struct tle\n#define MAX_TLES 200000\n\n/* As of late 2020,  NORAD numbers are stored in five digits.  There's\nsome possibility of that being bumped up to nine digits,  in which\ncase I'll have to revisit this code.         */\n\n#define MAX_NORAD_NUMBER 100000\n\nTLE\n   {\n   char name_line[80], line1[80], line2[80];\n   };\n\nint n_duplicates = 0, heavens_above_html_tles = 0;\n\nint load_tles_from_file( FILE *ifile, TLE *tles, char *already_found)\n{\n   int rval = 0;\n   char buff[80], prev_line[80];\n\n   *prev_line = '\\0';\n   while( fgets( buff, sizeof( buff), ifile))\n      {\n      if( *buff == '1' && heavens_above_html_tles &&\n                            !memcmp( buff + 69, \"<B></B>\", 7))\n         strcpy( buff + 69, \"\\n\");\n      if( *buff == '1' && strlen( buff) > 69 && buff[69] < ' ')\n         {\n         char buff2[80];\n         const int norad_number = atoi( buff + 2);\n\n         if( fgets( buff2, sizeof( buff2), ifile))\n            if( *buff2 == '2' && strlen( buff2) > 69 && buff2[69] < ' ')\n               {\n               if( !already_found[norad_number])\n                  {\n                  if( heavens_above_html_tles)     /* can't use HA names */\n                     *prev_line = '\\0';\n                  strcpy( tles[rval].name_line, prev_line);\n                  strcpy( tles[rval].line1, buff);\n                  strcpy( tles[rval].line2, buff2);\n                  already_found[norad_number] = 1;\n                  rval++;\n                  *buff = '\\0';\n                  }\n               else\n                  n_duplicates++;\n               }\n         }\n      strcpy( prev_line, buff);\n      }\n   return( rval);\n}\n\nFILE *test_fopen( const char *filename, const char *permits)\n{\n   FILE *rval = fopen( filename, permits);\n\n   if( !rval)\n      {\n      printf( \"%s not opened\\n\", filename);\n      exit( -1);\n      }\n   return( rval);\n}\n\nvoid show_tle( FILE *ofile, const TLE *tle)\n{\n   fprintf( ofile, \"%s\", tle->name_line);\n   fprintf( ofile, \"%s\", tle->line1);\n   fprintf( ofile, \"%s\", tle->line2);\n}\n\nstatic double get_perigee( const TLE *tle)\n{\n   const double ecc = atof( tle->line2 + 26) * 1e-7;\n   const double revs_per_day = atof( tle->line2 + 52);\n   const double semimajor = pow( revs_per_day, -2. / 3.);\n\n   return( semimajor * (1. - ecc));\n}\n\nstatic int compare_doubles( const char *buff1, const char *buff2)\n{\n   const double d1 = atof( buff1);\n   const double d2 = atof( buff2);\n   int rval;\n\n   if( d1 < d2)\n      rval = -1;\n   else if( d1 > d2)\n      rval = 1;\n   else\n      rval = 0;\n   return( rval);\n}\n\nint tle_compare( const TLE *tle1, const TLE *tle2, const char sort_method)\n{\n   int i, rval = 0;\n\n   switch( sort_method)\n      {\n      case 'n': case 'N':        /* sort by NORAD number */\n         rval = atoi( tle1->line1 + 2) - atoi( tle2->line1 + 2);\n         break;\n      case 'c': case 'C':        /* sort by COSPAR (international) desig */\n         if( tle1->line1[9] >= '5' && tle2->line1[9] < '5')\n            rval = -1;\n         else if( tle2->line1[9] >= '5' && tle1->line1[9] < '5')\n            rval = 1;\n         else        /* COSPAR IDs are from the same century */\n            rval = memcmp( tle1->line1 + 9, tle2->line1 + 9, 8);\n         break;\n      case 'm': case 'M':        /* sort by mean motion */\n         rval = compare_doubles( tle1->line2 + 52, tle2->line2 + 52);\n         break;\n      case 'e': case 'E':        /* sort by eccentricity */\n         for( i = 26; !rval && i < 33; i++)\n            rval = tle1->line2[i] - tle2->line2[i];\n         break;\n      case 'p': case 'P':        /* sort by epoch */\n         for( i = 18; !rval && i < 32; i++)\n            rval = tle1->line1[i] - tle2->line1[i];\n         break;\n      case 'i': case 'I':        /* sort by incl */\n         rval = compare_doubles( tle1->line2 + 8, tle2->line2 + 8);\n         break;\n      case 'o': case 'O':        /* sort by ascending node */\n         rval = compare_doubles( tle1->line2 + 17, tle2->line2 + 17);\n         break;\n      case 'q':\n         {\n         const double q1 = get_perigee( tle1);\n         const double q2 = get_perigee( tle2);\n\n         rval = ( q1 > q2 ? 1 : -1);\n         }\n         break;\n      }\n   if( sort_method >= 'A' && sort_method <= 'Z')\n      rval = -rval;\n   return( rval);\n}\n\n      /* MS Windows lacks the re-entrant qsort_r;  we have to use  */\n      /* plain old non-re-entrant qsort and a global variable.     */\n      /* BSD has it,  but with the wrong order of arguments.       */\n#ifdef __linux\n#define HAVE_REENTRANT_QSORT\n#endif\n\n#ifdef HAVE_REENTRANT_QSORT\nint tle_compare_for_qsort_r( const void *a, const void *b, void *c)\n{\n   return( tle_compare( (const TLE *)a, (const TLE *)b, *(char *)c));\n}\n#else\nstatic char comparison_method;\n\nint tle_compare_for_qsort( const void *a, const void *b)\n{\n   return( tle_compare( (const TLE *)a, (const TLE *)b, comparison_method));\n}\n#endif\n\n\nstatic void error_exit( void)\n{\n   printf( \"'mergetle' will merge TLEs from one or more files.  Optionally,\\n\");\n   printf( \"the output can be sorted.  For example:\\n\\n\");\n   printf( \"mergetle geosynch.tle molniya.tle visual.tle -se -oall.tle\\n\\n\");\n   printf( \"would create a file 'all.tle',  containing elements from the three\\n\");\n   printf( \"input .tle files,  sorted by NORAD number.  If a satellite appears\\n\");\n   printf( \"in more than one .tle,  the .tle from the first file on the command\\n\");\n   printf( \"line is used.  Options are:\\n\\n\");\n   printf( \"-sn, -sN       Sort output by ascending/descending NORAD number\\n\");\n   printf( \"-sc, -sC       Sort output by ascending/descending COSPAR desig\\n\");\n   printf( \"-sm, -sM       Sort output by ascending/descending mean motion\\n\");\n   printf( \"-se, -sE       Sort output by ascending/descending eccentricity\\n\");\n   printf( \"-sp, -sP       Sort output by ascending/descending epoch\\n\");\n   printf( \"-si, -sI       Sort output by ascending/descending inclination\\n\");\n   printf( \"-so, -sO       Sort output by ascending/descending ascending node\\n\");\n   printf( \"-sq            Sort output by ascending perigee\\n\");\n   printf( \"-o(filename)   Set name of output .tle file (default is out.tle)\\n\");\n   printf( \"-n             Remove names from input TLEs\\n\");\n   printf( \"-h             Remove HTML tags from input.  This allows you to extract\\n\");\n   printf( \"                 TLEs from certain Web pages.\\n\");\n   exit( -1);\n}\n\nint main( const int argc, const char **argv)\n{\n   TLE *tles = (TLE *)calloc( MAX_TLES, sizeof( TLE));\n   char *already_found = (char *)calloc( MAX_NORAD_NUMBER, sizeof( char));\n   int n_found = 0, i, strip_names = 0;\n   char sort_method = 0;\n   const char *output_filename = \"out.tle\";\n   FILE *ofile;\n\n   if( argc < 2)\n      error_exit( );\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-')\n         switch( argv[i][1])\n            {\n            case 'o':\n               output_filename = argv[i] + 2;\n               break;\n            case 's':\n               sort_method = argv[i][2];\n               break;\n            case 'n':\n               strip_names = 1;\n               printf( \"Names will be removed in output\\n\");\n               break;\n            case 'h':\n               heavens_above_html_tles = 1;\n               printf( \"HTML tags will be removed from input\\n\");\n               break;\n            default:\n               printf( \"Ignoring unknown option '%s'\\n\", argv[i]);\n               break;\n            }\n      else\n         {\n         FILE *ifile = test_fopen( argv[i], \"rb\");\n         int n;\n\n         n_duplicates = 0;\n         n = load_tles_from_file( ifile, tles + n_found, already_found);\n         printf( \"%d TLEs added from %s,  with %d duplicates found\\n\",\n                            n, argv[i], n_duplicates);\n         n_found += n;\n         fclose( ifile);\n         }\n   free( already_found);\n   if( strip_names)\n      for( i = 0; i < n_found; i++)\n         tles[i].name_line[0] = '\\0';\n#ifndef HAVE_REENTRANT_QSORT\n   comparison_method = sort_method;\n   qsort( tles, n_found, sizeof( TLE), tle_compare_for_qsort);\n#else\n   qsort_r( tles, n_found, sizeof( TLE), tle_compare_for_qsort_r, &sort_method);\n#endif\n   ofile = test_fopen( output_filename, \"wb\");\n   for( i = 0; i < n_found; i++)\n      show_tle( ofile, tles + i);\n}\n"
  },
  {
    "path": "msvc.mak",
    "content": "# Makefile for MSVC\nall:  dropouts.exe fix_tles.exe line2.exe mergetle.exe obs_test.exe \\\n   obs_tes2.exe out_comp.exe sat_eph.exe sat_id.exe  \\\n   test2.exe test_out.exe test_sat.exe tle2mpc.exe\n\nCOMMON_FLAGS=-nologo -W3 -EHsc -c -FD -D_CRT_SECURE_NO_WARNINGS\nRM=del\n\n!ifdef BITS_32\nBITS=32\n!else\nBITS=64\n!endif\n\nCFLAGS=-MT -O1 -D \"NDEBUG\" $(COMMON_FLAGS)\nLINK=link /nologo /stack:0x8800\n\nOBJS= sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \\\n     basics.obj get_el.obj common.obj tle_out.obj\n\ndropouts.exe: dropouts.obj\n   $(LINK) dropouts.obj\n\nfix_tles.exe: fix_tles.obj sat_code$(BITS).lib\n   $(LINK)    fix_tles.obj sat_code$(BITS).lib\n\nline2.exe: line2.obj observe.obj sat_code$(BITS).lib\n   $(LINK) line2.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib\n\nmergetle.exe: mergetle.obj\n   $(LINK) mergetle.obj\n\nobs_test.exe: obs_test.obj observe.obj sat_code$(BITS).lib\n   $(LINK)    obs_test.obj observe.obj sat_code$(BITS).lib\n\nobs_tes2.exe: obs_tes2.obj observe.obj sat_code$(BITS).lib\n   $(LINK)    obs_tes2.obj observe.obj sat_code$(BITS).lib\n\nout_comp.exe: out_comp.obj\n   $(LINK)    out_comp.obj\n\nsat_code$(BITS).lib: $(OBJS)\n   del sat_code$(BITS).lib\n   lib /OUT:sat_code$(BITS).lib $(OBJS)\n\nsat_id.exe: sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib\n   $(LINK)  sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib\n\nsat_eph.exe: sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib\n    $(LINK)  sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib\n\ntest2.exe: test2.obj sat_code$(BITS).lib\n   $(LINK) test2.obj sat_code$(BITS).lib\n\ntest_out.exe: test_out.obj sat_code$(BITS).lib\n   $(LINK)    test_out.obj sat_code$(BITS).lib\n\ntest_sat.exe: test_sat.obj sat_code$(BITS).lib\n   $(LINK)    test_sat.obj sat_code$(BITS).lib\n\ntle2mpc.exe: tle2mpc.obj observe.obj sat_code$(BITS).lib\n   $(LINK)   tle2mpc.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib\n\n.cpp.obj:\n   cl $(CFLAGS) $<\n\nclean:\n   del *.obj\n   del *.exe\n   del *.idb\n   del sat_code$(BITS).lib\n\ninstall:\n   copy norad.h ..\\myincl\n   copy sat_code$(BITS).lib ..\\lib\n"
  },
  {
    "path": "msvc_dll.mak",
    "content": "# MSVC makefile for a DLL version\n# NOTE: hasn't been used or updated in years;  some work required\n# before it would be fit for purpose.\n\nall:  test2.exe test_sat.exe obs_test.exe obs_tes2.exe sat_id.exe test_out.exe sm_sat.dll out_comp.exe\n\nout_comp.exe: out_comp.cpp\n   cl -W3 -Ox -nologo out_comp.cpp\n\ntest2.exe: test2.obj sat_code.lib\n   link test2.obj sat_code.lib\n\ntest_sat.exe: test_sat.obj sat_code.lib\n   cl -nologo test_sat.obj sat_code.lib\n\ntest_out.exe: test_out.obj tle_out.obj sat_code.lib\n   cl -nologo test_out.obj tle_out.obj sat_code.lib\n\nobs_test.exe: obs_test.obj observe.obj sat_code.lib\n   cl -nologo obs_test.obj observe.obj sat_code.lib\n\nobs_tes2.exe: obs_tes2.obj observe.obj sat_code.lib\n   cl -nologo obs_tes2.obj observe.obj sat_code.lib\n\nsat_id.exe:   sat_id.obj sat_util.obj sat_code.lib\n   cl -nologo sat_id.obj sat_util.obj sat_code.lib\n\nsat_code.lib: sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \\\n   basics.obj get_el.obj observe.obj common.obj\n   del sat_code.lib\n   del sat_code.dll\n   link /DLL /IMPLIB:sat_code.lib /DEF:sat_code.def /MAP:sat_code.map \\\n            sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \\\n            basics.obj get_el.obj observe.obj common.obj\n\nsm_sat.dll: sgp4.obj basics.obj get_el.obj common.obj\n   del sm_sat.lib\n   del sm_sat.dll\n   link /DLL /IMPLIB:sm_sat.lib /DEF:sm_sat.def /MAP:sm_sat.map \\\n            sgp4.obj basics.obj get_el.obj common.obj\n\n#CFLAGS=-W3 -c -LD -Ox -DRETAIN_PERTURBATION_VALUES_AT_EPOCH\nCFLAGS=-W3 -c -LD -Ox -nologo -D_CRT_SECURE_NO_WARNINGS\n\n.cpp.obj:\n   cl $(CFLAGS) $<\n\ncommon.obj:\n\nsgp.obj:\n\nsgp4.obj:\n\nsgp8.obj:\n\nsdp4.obj:\n\nsdp8.obj:\n\ndeep.obj:\n\nbasics.obj:\n\nget_el.obj:\n\nobserve.obj:\n\ntest2.obj:\n\ntest_out.obj:\n\ntest_sat.obj:\n\ntle_out.obj:\n\nobs_test.obj:\n\nobs_tes2.obj:\n\nsat_id.obj:\n\nclean:\n   del sat_code.dll\n   del *.obj\n   del *.exe\n   del *.idb\n   del sat_code.exp\n   del sat_code.map\n   del sat_code$(BITS).lib\n"
  },
  {
    "path": "norad.h",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  norad.h v. 01.beta 03/17/2001\n *\n *  Header file for norad.c\n */\n\n#ifndef NORAD_H\n#define NORAD_H 1\n\n/* #define RETAIN_PERTURBATION_VALUES_AT_EPOCH 1 */\n\n/* Two-line-element satellite orbital data */\ntypedef struct\n{\n  double epoch, xndt2o, xndd6o, bstar;\n  double xincl, xnodeo, eo, omegao, xmo, xno;\n  int norad_number, bulletin_number, revolution_number;\n  char classification;    /* \"U\" = unclassified;  only type I've seen */\n  char ephemeris_type;\n  char intl_desig[9];\n} tle_t;\n\n   /* NOTE: xndt2o and xndt6o are used only in the \"classic\" SGP, */\n   /* not in SxP4 or SxP8. */\n   /* epoch is a Julian Day,  UTC */\n   /* xmo = mean anomaly at epoch,  radians */\n   /* xno = mean motion at epoch,  radians/minute*/\n\n#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH\n   #define DEEP_ARG_T_PARAMS     87\n#else\n   #define DEEP_ARG_T_PARAMS     81\n#endif\n\n#define N_SGP_PARAMS          11\n#define N_SGP4_PARAMS         30\n#define N_SGP8_PARAMS         25\n#define N_SDP4_PARAMS        (10 + DEEP_ARG_T_PARAMS)\n#define N_SDP8_PARAMS        (11 + DEEP_ARG_T_PARAMS)\n\n/* 94 = maximum possible size of the 'deep_arg_t' structure,  in 8-byte units */\n/* You can use the above constants to minimize the amount of memory used,\n   but if you use the following constant,  you can be assured of having\n   enough memory for any of the five models: */\n\n#define N_SAT_PARAMS         (11 + DEEP_ARG_T_PARAMS)\n\n/* Byte 63 of the first line of a TLE contains the ephemeris type.  The */\n/* following five values are recommended,  but it seems the non-zero    */\n/* values are only used internally;  \"published\" TLEs all have type 0.  */\n/* However,  I've had occasion to produce SGP4 TLEs for high-fliers, in */\n/* cases where I couldn't get an SDP4 fit.                              */\n\n#define TLE_EPHEMERIS_TYPE_DEFAULT           0\n#define TLE_EPHEMERIS_TYPE_SGP               1\n#define TLE_EPHEMERIS_TYPE_SGP4              2\n#define TLE_EPHEMERIS_TYPE_SDP4              3\n#define TLE_EPHEMERIS_TYPE_SGP8              4\n#define TLE_EPHEMERIS_TYPE_SDP8              5\n\n#define SXPX_DPSEC_INTEGRATION_ORDER         0\n#define SXPX_DUNDEE_COMPLIANCE               1\n#define SXPX_ZERO_PERTURBATIONS_AT_EPOCH     2\n\n\n/* SDP4 and SGP4 can return zero,  or any of the following error/warning codes.\nThe 'warnings' result in a mathematically reasonable value being returned,\nand perigee within the earth is completely reasonable for an object that's\njust left the earth or is about to hit it.  The 'errors' mean that no\nreasonable position/velocity was determined.       */\n\n#define SXPX_ERR_NEARLY_PARABOLIC         -1\n#define SXPX_ERR_NEGATIVE_MAJOR_AXIS      -2\n#define SXPX_WARN_ORBIT_WITHIN_EARTH      -3\n#define SXPX_WARN_PERIGEE_WITHIN_EARTH    -4\n#define SXPX_ERR_NEGATIVE_XN              -5\n#define SXPX_ERR_CONVERGENCE_FAIL         -6\n\n/* Function prototypes */\n/* norad.c */\n\n         /* The Win32 version can be compiled to make a .DLL,  if the     */\n         /* functions are declared to be of type __stdcall... _and_ the   */\n         /* functions must be declared to be extern \"C\",  something I     */\n         /* overlooked and added 24 Sep 2002.  The DLL_FUNC macro lets    */\n         /* this coexist peacefully with other OSes.                      */\n\n#ifdef _WIN32\n#define DLL_FUNC __stdcall\n#else\n#define DLL_FUNC\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n\nvoid DLL_FUNC SGP_init( double *params, const tle_t *tle);\nint  DLL_FUNC SGP(  const double tsince, const tle_t *tle, const double *params,\n                                     double *pos, double *vel);\n\nvoid DLL_FUNC SGP4_init( double *params, const tle_t *tle);\nint  DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *params,\n                                     double *pos, double *vel);\n\nvoid DLL_FUNC SGP8_init( double *params, const tle_t *tle);\nint  DLL_FUNC SGP8( const double tsince, const tle_t *tle, const double *params,\n                                     double *pos, double *vel);\n\nvoid DLL_FUNC SDP4_init( double *params, const tle_t *tle);\nint  DLL_FUNC SDP4( const double tsince, const tle_t *tle, const double *params,\n                                     double *pos, double *vel);\n\nvoid DLL_FUNC SDP8_init( double *params, const tle_t *tle);\nint  DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *params,\n                                     double *pos, double *vel);\n\nint DLL_FUNC select_ephemeris( const tle_t *tle);\nint DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t *sat);\nint DLL_FUNC tle_checksum( const char *buff);\nvoid DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle);\n\nvoid DLL_FUNC sxpx_set_implementation_param( const int param_index,\n                                              const int new_param);\nvoid DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size);\nvoid DLL_FUNC lunar_solar_position( const double jd,\n                    double *lunar_xyzr, double *solar_xyzr);\n\n#ifdef __cplusplus\n}                       /* end of 'extern \"C\"' section */\n#endif\n\n         /* Following are in 'dynamic.cpp',  for C/C++ programs that want  */\n         /* to load 'sat_code.dll' and use its functions at runtime.  They */\n         /* only make sense in the Win32 world: */\n#ifdef _WIN32\nint SXPX_init( double *params, const tle_t *tle, const int sxpx_num);\nint SXPX( const double tsince, const tle_t *tle, const double *params,\n                               double *pos, double *vel, const int sxpx_num);\n#endif\n#endif\n"
  },
  {
    "path": "norad_in.h",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#ifndef NORAD_IN_H\n#define NORAD_IN_H\n\n/* Common \"internal\" arguments between deep-space functions;  users of  */\n/* the satellite routines shouldn't need to bother with any of this     */\n\ntypedef struct\n{\n  double\n  /* Common between SGP4 and SDP4: */\n  aodp, cosio, sinio, omgdot, xmdot, xnodot, xnodp,\n  /* Used by dpinit part of Deep() */\n  eosq, betao, cosio2, sing, cosg, betao2,\n\n  /* Used by dpsec and dpper parts of Deep() */\n  xll, omgadf, xnode, em, xinc, xn, t,\n\n       /* 'd####' secular coeffs for 12-hour, e>.5 orbits: */\n   d2201, d2211, d3210, d3222, d4410, d4422, d5220, d5232, d5421, d5433,\n      /* formerly static to Deep( ),   but more logically part of this struct: */\n   atime, del1, del2, del3, e3, ee2, omegaq, pe, pgh, ph, pinc, pl, preep,\n   savtsn, se2, se3, sgh2, sgh3, sgh4, sh2, sh3, si2, si3, sl2, sl3,\n   sl4, sse, ssg, ssh, ssi, ssl, thgr, xfact, xgh2, xgh3, xgh4, xh2,\n   xh3, xi2, xi3, xl2, xl3, xl4, xlamo, xli, xni, xnq,\n   zmol, zmos;\n\n         /* Epoch offsets,  described by Rob Matson,  added by BJG, */\n         /* then commented out;  I don't think they really ought to */\n         /* be used... */\n#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH\n    double pe0, pinc0, pl0, pgh0, ph0;\n    int solar_lunar_init_flag;\n#endif\n    int resonance_flag, synchronous_flag;\n} deep_arg_t;\n\ndouble FMod2p( const double x);\nvoid Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg);\nvoid Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg);\nvoid Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg);\n\nint sxpx_posn_vel( const double xnode, const double a, const double e,\n      const double cosio, const double sinio,\n      const double xincl, const double omega,\n      const double xl, double *pos, double *vel);\n\ntypedef struct\n{\n   double coef, coef1, tsi, s4, unused_a3ovk2, eta;\n} init_t;\n\nvoid sxpx_common_init( double *params, const tle_t *tle,\n                                  init_t *init, deep_arg_t *deep_arg);\n\n/* Table of constant values */\n#define pi               3.141592653589793238462643383279502884197\n#define twopi            (pi*2.)\n#define e6a              1.0E-6\n#define two_thirds       (2. / 3.)\n#define xj3             -2.53881E-6\n#define minus_xj3        2.53881E-6\n#define earth_radius_in_km           6378.135\n#ifndef minutes_per_day\n   #define minutes_per_day  1440.\n#endif\n#define ae                  1.0\n#define xj2                 1.082616e-3\n#define ck2                 (.5 * xj2 * ae * ae)\n\n      /* xke^2 = earth GM,  in (earth radii)^3/minutes^2.  */\n#ifdef OLD_CONSTANTS\n#define ck4      6.209887E-7\n#define s        1.012229\n#define qoms2t   1.880279E-09\n#define xke      7.43669161E-2\n#else\n#define xj4      (-1.65597e-6)\n#define ck4      (-.375 * xj4 * ae * ae * ae * ae)\n#define s_const  (ae * (1. + 78. / earth_radius_in_km))\n#define qoms2t   1.880279159015270643865e-9\n#define xke      0.0743669161331734132\n#endif\n\n#define a3ovk2   (minus_xj3/ck2*ae*ae*ae)\n\n#endif         /* #ifndef NORAD_IN_H */\n"
  },
  {
    "path": "nu2vect.c",
    "content": "/* Code to read in the Spektr-RG state vectors from .nu files at\n\nftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/\n\n   and output into a form ingestible by Find_Orb.  (Note that these\nhaven't been updated for a while;  we've been tracking Spektr-RG\nsolely through observed astrometry.)  Input lines give\n\n0.000    (always zero,  means J2000)\n1        (loop number;  ignore)\n2.021030100000E+7    ( = 20210301 = 2021 Mar 01)\n 1.649335000000E+05  ( = 16:49:33.5 Moscow time (three hours ahead of UTC))\n-1.190093387974E+03  (x-coord, geocentric, equatorial J2000,  in thousands of km)\n 9.733125144680E+02  (y-coord,  same system)\n 4.956578219661E+02  (z-coord,  same system)\n-7.639675611173E-02  (x-velocity, in km/s)\n-6.678070845750E-02  (y-velocity)\n-2.372125806637E-01  (z-velocity)\n 0.000000000000E+00  (ballistic coefficient)\n 1.246012245177E-05  (solar radiation coefficient, unitless)\n\n   Note that the file name gives the UTC,  and the time given within\nthe file is three hours ahead of that.  The positions match those\ndetermined by optical astrometry to within a few kilometers.\n\n   The above solar radiation coefficient means that the sun's gravity\nis counteracted by a force 1.24601e-5 times as great.  It corresponds\napproximately to an area/mass ratio of 0.015 m^2/kg,  which is\nquite close to what we've been getting from the optical astrometry\norbit solution.\n\n   Compile with\n\ngcc -Wextra -Wall -O3 -pedantic nu2vect.c -I../include -L../lib -o nu2vect -llunar\n\n   Python code to convert .nu files to STK ephemeris files is at\n\nhttps://github.com/Satsir/STK/blob/main/nuToEph.py     */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n#include \"stringex.h\"\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile = (argc == 2 ? fopen( argv[1], \"rb\") : NULL);\n   char buff[90];\n   char command[200];\n   int i;\n\n   if( !ifile)\n      fprintf( stderr, \"See comments at start of 'nu2vect.c' for usage\\n\");\n   assert( ifile);\n   for( i = 0; i < 10; i++)\n      if( fgets( buff, sizeof( buff), ifile))\n         {\n         const double ival = atof( buff);\n\n         switch( i)\n            {\n            case 2:\n               {\n               const int day = (int)ival;\n\n               snprintf_err( buff, sizeof( buff), \"%04d-%02d-%02d\",\n                        day / 10000, (day / 100) % 100, day % 100);\n               printf( \"Date : %s\\n\", buff);\n               snprintf_err( command, sizeof( command),\n                       \"find_orb \\\"-oSpektr-RG = 2019-040A = NORAD 44432\\\" -v%sT\", buff);\n               }\n               break;\n            case 3:\n               {\n               const int millisec = (int)( ival * 1000.);\n\n               snprintf_err( buff, sizeof( buff), \"%02d:%02d:%02d.%03d\",\n                        millisec / 10000000,\n                        (millisec / 100000) % 100,\n                        (millisec / 1000) % 100, millisec % 100);\n               printf( \"Time : %s\\n\", buff);\n               strlcat_error( command, buff);\n               strlcat_error( command, \"-3h\");   /* correction for Moscow time */\n               }\n               break;\n            case 4: case 5: case 6:\n               printf( \"  Posn %f km\\n\", ival * 1000.);\n               snprintf_append( command, sizeof( command), \",%.2f\", ival * 1000.);\n               break;\n            case 7: case 8: case 9:\n               printf( \"  Vel  %f km/s\\n\", ival);\n               snprintf_append( command, sizeof( command), \",%.7f\", ival);\n               break;\n            break;\n            }\n         }\n   fclose( ifile);\n   printf( \"%s,eq,geo,km,s\\n\", command);\n   return( 0);\n}\n"
  },
  {
    "path": "nu_readme.txt",
    "content": "(Translation/additions to ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/00_read.me .\nThis also applies to the files for Spektr-R in the\nftp://ftp.kiam1.rssi.ru/pub/gps/spectr-r/nu/ directory.)\n\nFormat of .nu files :\n\n\nLine 1:  0.000000000000E+00   // 0 = coordinates are in J2000, geocentric, equatorial\nLine 2:  1.000000000000E+00   // orbit number (always zero for Spektr-RG)\nLine 3:  2.011111500000E+07   // Epoch date,  YYYYMMDD (example is 2011 11 15)\nLine 4:  1.492674329226E+04   // Epoch time, HH:MM:SS (example is 01:49:26.74329226)\nLine 5:  5.174588077311E+00   // x-coordinate,  in thousands of kilometers\nLine 6: -2.277098498781E+00   // y-coordinate\nLine 7: -3.461034587339E+00   // z-coordinate\nLine 8:  4.762109598043E+00   // x-velocity,  in kilometers/second\nLine 9:  4.109503042673E+00   // y-velocity\nLine 10: 4.560902129453E+00   // z-velocity\nLine 11: 3.000000000000E-02   // Ballistic coefficient,  m^3/seconds^2/kilograms (?)\nLine 12: 1.248000000000E-05   // Solar radiation coefficient,  unitless\n\n   For Spektr-R,  the orbit number is non-zero and gives the number\nof completed orbits.  It is not defined for Spektr-RG.\n\n   Note that the file name gives the epoch in UTC.  The epoch given\nwithin the file is three hours ahead of that (Moscow time).  The\npositions match those determined by optical astrometry to within about\n20 kilometers.  It is possible that the epoch is in TT (plus three\nhours);  the difference is below what I can detect.\n\n   The above solar radiation coefficient means that the sun's gravity\nis counteracted by a force 1.248e-5 times as great.  It corresponds\napproximately to an area/mass ratio of 0.015 m^2/kg,  which is\nquite close to what we've been getting from the optical astrometry\norbit solution.\n"
  },
  {
    "path": "obs_tes2.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n    obs_tes2.cpp     12 December 2002\n\n   (Revised slightly December 2012 to fix compiler warning errors.)\n\n   An example 'main' function illustrating how to find which satellite(s)\nare within a given radius of a given RA/dec,  as seen from a given\npoint.  The code reads in a TLE file (name provided as the first\ncommand-line argument).  Details of the observer position,  search\nradius,  date/time,  and RA/dec are provided on the command line.\nFor example:\n\nobs_tes2 alldat.tle -l44.01,-69.9,10 -p90,30 -j2452623.5 -r10\n\n   would hunt through the TLE element file 'alldat.tle' for satellites\nvisible from latitude +44.01,  longitude -69.9,  altitude 10 metres;\nat RA=90 degrees (6h),  dec=+30;  on JD 2452623.5 (UTC);  within a\nten-degree search radius.  (All of these are the default values.)\nThe output looks like this:\n\nNORAD  Int'l     RA (J2000) dec    Delta Radius  PA Speed\n08593U 74089DG  88.7235  22.9622   2293.0  7.15 225 10.75\n15830U 85049D   82.5051  34.9711  32143.6  8.99  34  0.24\n17642U 81053LQ  88.1471  32.6585   1428.5  3.24 213 17.60\n21833U 91088A   80.6400  27.9649  36216.6  9.58  87  0.17\n\n   ...with 'delta'=distance to satellite in km,  'radius'=angular\ndistance in degrees from the search point,  'PA' = position angle\nof motion, 'Speed' = apparent angular rate of motion in\narcminutes/second (or degrees/minute). */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <math.h>\n#include \"norad.h\"\n#include \"observe.h\"\n\n#define PI 3.141592653589793238462643383279\n#define TIME_EPSILON (1./86400.)\n\nint main( const int argc, const char **argv)\n{\n   const char *tle_file_name = ((argc == 1) ? \"alldat.tle\" : argv[1]);\n   FILE *ifile = fopen( tle_file_name, \"rb\");\n   char line1[100], line2[100];\n   double lat = 44.01, lon = -69.9, ht_in_meters = 10.;\n   double jd = 2452623.5;   /* 15 Dec 2002 0h UT */\n   double search_radius = 10.;     /* default to ten-degree search */\n   double target_ra = 90., target_dec = 30.;  /* default search is at RA=6h, dec=+30 */\n   double rho_sin_phi, rho_cos_phi, observer_loc[3], observer_loc2[3];\n   int i, header_line_shown = 0;\n\n   if( !ifile)\n      {\n      printf( \"Couldn't open input file %s\\n\", tle_file_name);\n      exit( -1);\n      }\n\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-')\n         switch( argv[i][1])\n            {\n            case 'l':\n               sscanf( argv[i] + 2, \"%lf,%lf,%lf\", &lat, &lon, &ht_in_meters);\n               break;\n            case 'p':\n               sscanf( argv[i] + 2, \"%lf,%lf\", &target_ra, &target_dec);\n               break;\n            case 'j':\n               jd = atof( argv[i] + 2);\n               break;\n            case 'r':\n               search_radius = atof( argv[i] + 2);\n               break;\n            default:\n               printf( \"Unrecognized command-line option '%s'\\n\", argv[i]);\n               exit( -2);\n               break;\n            }\n\n            /* Figure out where the observer _really_ is,  in Cartesian */\n            /* coordinates of date: */\n   earth_lat_alt_to_parallax( lat * PI / 180., ht_in_meters, &rho_cos_phi,\n                                                             &rho_sin_phi);\n   observer_cartesian_coords( jd,\n                lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc);\n   observer_cartesian_coords( jd + TIME_EPSILON,\n                lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc2);\n   target_ra *= PI / 180.;\n   target_dec *= PI / 180.;\n\n   if( fgets( line1, sizeof( line1), ifile))\n      while( fgets( line2, sizeof( line2), ifile))\n         {\n         tle_t tle;     /* Structure for two-line elements set for satellite */\n\n         if( !parse_elements( line1, line2, &tle))    /* hey! we got a TLE! */\n            {\n            int is_deep = select_ephemeris( &tle);\n            double sat_params[N_SAT_PARAMS], radius, d_ra, d_dec;\n            double ra, dec, dist_to_satellite, t_since;\n            double pos[3]; /* Satellite position vector */\n            double unused_delta2;\n\n            t_since = (jd - tle.epoch) * 1440.;\n            if( is_deep)\n               {\n               SDP4_init( sat_params, &tle);\n               SDP4( t_since, &tle, sat_params, pos, NULL);\n               }\n            else\n               {\n               SGP4_init( sat_params, &tle);\n               SGP4( t_since, &tle, sat_params, pos, NULL);\n               }\n            get_satellite_ra_dec_delta( observer_loc, pos,\n                                    &ra, &dec, &dist_to_satellite);\n            epoch_of_date_to_j2000( jd, &ra, &dec);\n            d_ra = (ra - target_ra + PI * 4.);\n            while( d_ra > PI)\n               d_ra -= PI + PI;\n            d_dec = dec - target_dec;\n            radius = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI;\n            if( radius < search_radius)      /* good enough for us! */\n               {\n               double speed, posn_ang_of_motion;\n\n               line1[16] = '\\0';\n\n               if( !header_line_shown)\n                  {\n                  printf( \"NORAD  Int'l     RA (J2000) dec    Delta Radius  PA Speed\\n\");\n                  header_line_shown = 1;\n                  }\n                                 /* Compute position one second later,  so we */\n                                 /* can show speed/PA of motion: */\n               t_since += TIME_EPSILON * 1440.;\n               if( is_deep)\n                  SDP4( t_since, &tle, sat_params, pos, NULL);\n               else\n                  SGP4( t_since, &tle, sat_params, pos, NULL);\n               get_satellite_ra_dec_delta( observer_loc2, pos,\n                                       &d_ra, &d_dec, &unused_delta2);\n               epoch_of_date_to_j2000( jd, &d_ra, &d_dec);\n               d_ra -= ra;\n               d_dec -= dec;\n               while( d_ra > PI)\n                  d_ra -= PI + PI;\n               while( d_ra < -PI)\n                  d_ra += PI + PI;\n               d_ra *= cos( dec);\n               posn_ang_of_motion = atan2( d_ra, d_dec);\n               if( posn_ang_of_motion < 0.)\n                  posn_ang_of_motion += PI + PI;\n               speed = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI;\n                        /* Put RA into 0 to 2pi range: */\n               ra = fmod( ra + PI * 10., PI + PI);\n               printf( \"%s %8.4f %8.4f %8.1f %5.2f %3d %5.2f\\n\",\n                        line1 + 2, ra * 180. / PI, dec * 180. / PI,\n                        dist_to_satellite, radius,\n                        (int)(posn_ang_of_motion * 180 / PI),\n                        speed * 60.);\n                              /* \"Speed\" is displayed in arcminutes/second,\n                                 or in degrees/minute */\n               }\n            }\n         strcpy( line1, line2);\n         }\n   fclose( ifile);\n   return( 0);\n} /* End of main() */\n\n"
  },
  {
    "path": "obs_test.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n    obs_test.cpp     23 September 2002\n\n   (Revised slightly December 2012 to fix compiler warning errors.)\n\n   An example 'main' function illustrating how to get topocentric\nephemerides for artificial satellites,  using the basic satellite\ncode plus the add-on topocentric functions.  The code reads the\nfile 'obs_test.txt',  getting commands setting the observer lat/lon\nand altitude and time of observation.  When it gets a command\nsetting a particular JD,  it computes the topocentric RA/dec/dist\nand prints them out.\n\n   At present,  'obs_test.txt' sets up a lat/lon/height in Bowdoinham,\nMaine,  corporate headquarters of Project Pluto,  and computes the\nposition of one low-orbit satellite (ISS) and one high-orbit satellite\n(Cosmos 1966 rocket booster).  You should get :\n\nNear-Earth type Ephemeris (SGP4) selected:\nObject 25544U 98067A, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000\nRA 350.1615 (J2000) dec -24.0241 dist 1867.97481 km\nDeep-Space type Ephemeris (SDP4) selected:\nObject 19448U 88076D, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000\nRA 3.5743 (J2000) dec 30.4293 dist 32114.83370 km\n*/\n\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"norad.h\"\n#include \"observe.h\"\n\n#define PI 3.141592653589793238462643383279\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile = fopen( (argc == 1) ? \"obs_test.txt\" : argv[1], \"rb\");\n   tle_t tle; /* Pointer to two-line elements set for satellite */\n   char line1[100], line2[100];\n   double lat = 0., lon = 0., ht_in_meters = 0., jd = 0.;\n   int ephem = 1;       /* default to SGP4 */\n\n   if( !ifile)\n      {\n      printf( \"Couldn't open input OBS_TEST.TXT file\\n\");\n      exit( -1);\n      }\n   if( fgets( line1, sizeof( line1), ifile))\n      while( fgets( line2, sizeof( line2), ifile))\n         {\n         int err_val;\n\n         if( !memcmp( line2, \"Ephem \", 6))\n            ephem = (line2[6] - '0');\n         else if( !memcmp( line2, \"JD \", 3))\n            jd = atof( line2 + 3);\n         else if( !memcmp( line2, \"ht \", 3))\n            ht_in_meters = atof( line2 + 3);\n         else if( !memcmp( line2, \"lat \", 4))\n            lat = atof( line2 + 4) * PI / 180.;     /* cvt degrees to radians */\n         else if( !memcmp( line2, \"lon \", 4))\n            lon = atof( line2 + 4) * PI / 180.;\n         else if( (err_val = parse_elements( line1, line2, &tle)) >= 0)\n            {                  /* hey! we got a TLE! */\n            int is_deep = select_ephemeris( &tle);\n            const char *ephem_names[5] = { \"SGP \", \"SGP4\", \"SGP8\", \"SDP4\", \"SDP8\" };\n            double sat_params[N_SAT_PARAMS], observer_loc[3];\n            double rho_sin_phi, rho_cos_phi;\n            double ra, dec, dist_to_satellite, t_since;\n            double pos[3]; /* Satellite position vector */\n\n            if( err_val)\n               printf( \"WARNING: TLE parsing error %d\\n\", err_val);\n            earth_lat_alt_to_parallax( lat, ht_in_meters, &rho_cos_phi,\n                                                          &rho_sin_phi);\n            observer_cartesian_coords( jd, lon, rho_cos_phi, rho_sin_phi,\n                        observer_loc);\n            if( is_deep && (ephem == 1 || ephem == 2))\n               ephem += 2;    /* switch to an SDx */\n            if( !is_deep && (ephem == 3 || ephem == 4))\n               ephem -= 2;    /* switch to an SGx */\n            if( is_deep)\n               printf(\"Deep-Space type Ephemeris (%s) selected:\\n\",\n                                          ephem_names[ephem]);\n            else\n               printf(\"Near-Earth type Ephemeris (%s) selected:\\n\",\n                                          ephem_names[ephem]);\n\n            /* Calling of NORAD routines */\n            /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8)   */\n            /* will be called in turn with the appropriate TLE set */\n            t_since = (jd - tle.epoch) * 1440.;\n            switch( ephem)\n               {\n               case 0:\n                  SGP_init( sat_params, &tle);\n                  err_val = SGP( t_since, &tle, sat_params, pos, NULL);\n                  break;\n               case 1:\n                  SGP4_init( sat_params, &tle);\n                  err_val = SGP4( t_since, &tle, sat_params, pos, NULL);\n                  break;\n               case 2:\n                  SGP8_init( sat_params, &tle);\n                  err_val = SGP8( t_since, &tle, sat_params, pos, NULL);\n                  break;\n               case 3:\n                  SDP4_init( sat_params, &tle);\n                  err_val = SDP4( t_since, &tle, sat_params, pos, NULL);\n                  break;\n               case 4:\n                  SDP8_init( sat_params, &tle);\n                  err_val = SDP8( t_since, &tle, sat_params, pos, NULL);\n                  break;\n               default:\n                  printf( \"? How did we get here? ephem = %d\\n\", ephem);\n                  err_val = 0;\n                  break;\n               }\n            if( err_val)\n               printf( \"Ephemeris error %d\\n\", err_val);\n            line1[15] = '\\0';\n            printf( \"Object %s, as seen from lat %.5f lon %.5f, JD %.5f\\n\",\n                     line1 + 2, lat * 180. / PI, lon * 180. / PI, jd);\n            get_satellite_ra_dec_delta( observer_loc, pos,\n                                    &ra, &dec, &dist_to_satellite);\n            epoch_of_date_to_j2000( jd, &ra, &dec);\n            printf( \"RA %.4f (J2000) dec %.4f dist %.5f km\\n\",\n                        ra * 180. / PI, dec * 180. / PI, dist_to_satellite);\n            }\n         strcpy( line1, line2);\n         }\n   fclose( ifile);\n   return( 0);\n} /* End of main() */\n\n"
  },
  {
    "path": "obs_test.txt",
    "content": "#  Input file for 'obs_test'\r\n#  Set up desired lat/lon/ht in meters/JD,  specify a TLE,  and\r\n#  the topocentric RA/dec/distance will be shown.\r\nlat 44.01\r\nlon -69.9\r\n# Western longitudes are negative,  eastern positive.  The above\r\n# lat/lon corresponds to my location in Bowdoinham,  Maine,  in the\r\n# northeastern United States.\r\nht 100\r\nJD 2452541.5         /* 24 Sep 2002 0h UT */\r\nISS\r\n1 25544U 98067A   02256.70033192  .00045618  00000-0  57184-3 0  1499\r\n2 25544  51.6396 328.6851 0018421 253.2171 244.7656 15.59086742217834\r\n\r\n# Above should give RA = 350.1615 deg, dec = -24.0241, dist = 1867.97542 km\r\n\r\n# Now compute a second,  higher satellite for the same place/time:\r\nCosmos 1966 Rk\r\n1 19448U 88076D   02255.52918163 -.00000002  00000-0  10000-3 0  4873\r\n2 19448  65.7943 338.1906 7142558 193.4853 125.7046  2.04085818104610\r\n\r\n# Above should give RA = 3.5743, dec = 30.4293, dist = 32114.83063 km\r\n"
  },
  {
    "path": "observe.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"observe.h\"\n\n/*  Assorted functions useful in conjunction with the satellite code\n   library for determining where the _observer_,  as well as the _target_,\n   happens to be.  Combine the two positions,  and you can get the\n   distance/RA/dec of the target as seen by the observer. */\n\n#define PI 3.141592653589793238462643383279\n#define EARTH_MAJOR_AXIS 6378140.\n#define EARTH_MINOR_AXIS 6356755.\n#define EARTH_AXIS_RATIO (EARTH_MINOR_AXIS / EARTH_MAJOR_AXIS)\n\n      /* function for Greenwich sidereal time,  ripped from 'deep.cpp' */\n\nstatic inline double ThetaG( double jd)\n{\n  /* Reference:  The 1992 Astronomical Almanac, page B6. */\n  const double omega_E = 1.00273790934;\n                   /* Earth rotations per sidereal day (non-constant) */\n  const double UT = fmod( jd + .5, 1.);\n  const double seconds_per_day = 86400.;\n  const double jd_2000 = 2451545.0;   /* 1.5 Jan 2000 = JD 2451545. */\n  double t_cen, GMST, rval;\n\n  t_cen = (jd - UT - jd_2000) / 36525.;\n  GMST = 24110.54841 + t_cen * (8640184.812866 + t_cen *\n                           (0.093104 - t_cen * 6.2E-6));\n  GMST = fmod( GMST + seconds_per_day * omega_E * UT, seconds_per_day);\n  if( GMST < 0.)\n     GMST += seconds_per_day;\n  rval = 2. * PI * GMST / seconds_per_day;\n\n  return( rval);\n} /*Function thetag*/\n\nvoid DLL_FUNC observer_cartesian_coords( const double jd, const double lon,\n              const double rho_cos_phi, const double rho_sin_phi,\n              double *vect)\n{\n   const double angle = lon + ThetaG( jd);\n\n   *vect++ = cos( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.;\n   *vect++ = sin( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.;\n   *vect++ = rho_sin_phi               * EARTH_MAJOR_AXIS / 1000.;\n}\n\nvoid DLL_FUNC earth_lat_alt_to_parallax( const double lat,\n                    const double ht_in_meters,\n                    double *rho_cos_phi, double *rho_sin_phi)\n{\n   const double u = atan( sin( lat) * EARTH_AXIS_RATIO / cos( lat));\n\n   *rho_sin_phi = EARTH_AXIS_RATIO * sin( u) +\n                           (ht_in_meters / EARTH_MAJOR_AXIS) * sin( lat);\n   *rho_cos_phi = cos( u) + (ht_in_meters / EARTH_MAJOR_AXIS) * cos( lat);\n}\n\nvoid DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc,\n                                 const double *satellite_loc, double *ra,\n                                 double *dec, double *delta)\n{\n   double vect[3], dist2 = 0.;\n   int i;\n\n   for( i = 0; i < 3; i++)\n      {\n      vect[i] = satellite_loc[i] - observer_loc[i];\n      dist2 += vect[i] * vect[i];\n      }\n   *delta = sqrt( dist2);\n   *ra = atan2( vect[1], vect[0]);\n   if( *ra < 0.)\n      *ra += PI + PI;\n   *dec = asin( vect[2] / *delta);\n}\n\n/* Formulae from Meeus' _Astronomical Algorithms_ for approximate precession.\nMore than accurate enough for our purposes.  */\n\nstatic void precess( const double t_centuries, double *ra, double *dec)\n{\n   const double m = (3.07496 + .00186 * t_centuries / 2.) * (PI / 180.) / 240.;\n   const double n = (1.33621 - .00057 * t_centuries / 2.) * (PI / 180.) / 240.;\n   const double ra_rate  = m + n * sin( *ra) * tan( *dec);\n   const double dec_rate = n * cos( *ra);\n\n   *ra -= t_centuries * ra_rate * 100.;\n   *dec -= t_centuries * dec_rate * 100.;\n}\n\nvoid DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec)\n{\n   const double t_centuries = (jd - 2451545.) / 36525.;\n\n   precess( t_centuries, ra, dec);\n}\n\nvoid DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec)\n{\n   const double t_centuries = (jd - 2451545.) / 36525.;\n\n   precess( -t_centuries, ra, dec);\n}\n"
  },
  {
    "path": "observe.h",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#ifdef _WIN32\n#define DLL_FUNC __stdcall\n#else\n#define DLL_FUNC\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\nvoid DLL_FUNC earth_lat_alt_to_parallax( const double lat,\n                    const double ht_in_meters,\n                    double *rho_cos_phi, double *rho_sin_phi);\nvoid DLL_FUNC observer_cartesian_coords( const double jd, const double lon,\n              const double rho_cos_phi, const double rho_sin_phi,\n              double *vect);\nvoid DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc,\n                                 const double *satellite_loc, double *ra,\n                                 double *dec, double *delta);\nvoid DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec);\nvoid DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec);\n\n#ifdef __cplusplus\n}                       /* end of 'extern \"C\"' section */\n#endif\n"
  },
  {
    "path": "out_comp.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n\nstatic int is_position_line( const char *buff)\n{\n   int rval = 0;\n\n   if( strlen( buff) > 73 && buff[73] < ' ' &&\n            buff[29] == '.' && buff[46] == '.' && buff[63] == '.')\n      rval = 1;\n   return( rval);\n}\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile1 = fopen( argv[1], \"rb\");\n   FILE *ifile2 = fopen( argv[2], \"rb\");\n   char buff1[80], buff2[80];\n   int line = 0, n_valid = 0, worst_line = -1;\n   double max_diff2 = 0.;\n\n   if( !ifile1)\n      printf( \"%s not opened\\n\", argv[1]);\n   if( !ifile2)\n      printf( \"%s not opened\\n\", argv[2]);\n   if( argc < 3 || !ifile1 || !ifile2)\n      {\n      printf( \"out_comp needs two files of output from test_sat to compare.\\n\");\n      exit( -1);\n      }\n\n   while( fgets( buff1, sizeof( buff1), ifile1) &&\n          fgets( buff2, sizeof( buff2), ifile2))\n      {\n      line++;\n      if( is_position_line( buff1) && is_position_line( buff2))\n         {\n         double diff2 = 0., delta;\n         int i;\n\n         n_valid++;\n         for( i = 22; i < 60; i += 17)\n            {\n            delta = atof( buff1 + i) - atof( buff2 + i);\n            diff2 += delta * delta;\n            }\n         if( diff2 > max_diff2)\n            {\n            max_diff2 = diff2;\n            worst_line = line;\n            }\n         }\n      }\n   fclose( ifile1);\n   fclose( ifile2);\n   printf( \"%d lines read in; %d had positions\\n\", line, n_valid);\n   printf( \"Max difference: %.8f km at line %d\\n\",\n                                  sqrt( max_diff2), worst_line);\n\n   return( 0);\n}\n"
  },
  {
    "path": "sat_code.def",
    "content": "LIBRARY sat_code\r\nEXPORTS\r\n   SGP_init                          @1\r\n   SGP4_init                         @2\r\n   SGP8_init                         @3\r\n   SDP4_init                         @4\r\n   SDP8_init                         @5\r\n   SGP                               @6\r\n   SGP4                              @7\r\n   SGP8                              @8\r\n   SDP4                              @9\r\n   SDP8                              @10\r\n   select_ephemeris                  @11\r\n   parse_elements                    @12\r\n   observer_cartesian_coords         @14\r\n   get_satellite_ra_dec_delta        @15\r\n   epoch_of_date_to_j2000            @16\r\n   tle_checksum                      @17\r\n   sxpx_set_implementation_param     @18\r\n   sxpx_set_dpsec_integration_step   @19\r\n   sxpx_library_version              @20\r\n   j2000_to_epoch_of_date            @21\r\n"
  },
  {
    "path": "sat_eph.c",
    "content": "#include <stdio.h>\n#include <ctype.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <assert.h>\n#include <math.h>\n#include <time.h>\n#if defined( _WIN32) || defined( __WATCOMC__)\n   #include \"zlibstub.h\"\n#else\n   #include <zlib.h>\n#endif\n#include \"watdefs.h\"\n#include \"afuncs.h\"\n#include \"comets.h\"\n#include \"date.h\"\n#include \"norad.h\"\n#include \"mpc_func.h\"\n#include \"stringex.h\"\n#include \"observe.h\"\n\n/* Code to generate topocentric ephemerides from TLE data,  mostly focussed\non the TLEs provided in https://www.github.com/Bill-Gray/tles. The program\ncan be compiled for standalone use or for use with the on-line artsat\nephemeris service at https://www.projectpluto.com/sat_eph.htm (q.v.). */\n\n#define PI 3.1415926535897932384626433832795028841971693993751058209749445923\n\ntypedef struct\n{\n   double lat, lon, alt, rho_sin_phi, rho_cos_phi;\n   double jd_start, jd_end, step_size;\n   int n_steps;\n   const char *desig;\n} ephem_t;\n\nstatic int verbose = 0;\n\nstatic double ra_offset = 0., dec_offset = 0.;\n\nstatic char *gzgets_trimmed( char *buff, const int buffsize, gzFile ifile)\n{\n   char *rval = gzgets( ifile, buff, buffsize);\n\n   if( rval)\n      {\n      size_t i = 0;\n\n      while( rval[i] != 10 && rval[i] != 13 && rval[i])\n         i++;\n      while( i && rval[i - 1] == ' ')\n         i--;        /* drop trailing spaces */\n      rval[i] = '\\0';\n      }\n   return( rval);\n}\n\nstatic void show_base_60( char *buff, const unsigned n_millisec)\n{\n   snprintf( buff, 15, \"%03u %02u %02u.%03u\",\n               n_millisec / 3600000u, (n_millisec / 60000u) % 60u,\n               (n_millisec / 1000u) % 60u, n_millisec % 1000u);\n}\n\nstatic void put_ra_in_buff( char *buff, double ra)\n{\n   ra = fmod( ra, 2. * PI);\n   if( ra < 0.)\n      ra += PI + PI;\n   show_base_60( buff, (unsigned)( 3600. * 1000. * ra * 12. / PI));\n   memmove( buff, buff + 1, strlen( buff));     /* remove leading zero */\n}\n\nstatic void put_dec_in_buff( char *buff, const double dec)\n{\n   show_base_60( buff, (unsigned)( 3600. * 1000. * fabs( dec) * 180. / PI));\n   *buff = (dec > 0. ? '+' : '-');\n}\n\nstatic double angle_between( const double *a, const double *b)\n{\n   const double cos_ang = dot_product( a, b) /\n                              sqrt( dot_product( a, a) * dot_product( b, b));\n   double rval = acose( cos_ang);\n\n   return( rval * 180. / PI);\n}\n\nstatic inline bool desig_match( const tle_t *tle, const char *desig)\n{\n   size_t i = 0;\n   bool rval = false;\n\n   while( isdigit( desig[i]))\n      i++;\n   if( i == 5)\n      {\n      if( !desig[i])       /* desig is all digits -> it's the NORAD # */\n         rval = (atoi( desig) == tle->norad_number);\n      else\n         {\n         i = strlen( desig);\n         if( i > 5 && i < 9)\n            rval = !memcmp( tle->intl_desig, desig, i) && tle->intl_desig[i] <= ' ';\n         }\n      }\n   return( rval);\n}\n\n/* Generates unit vector in the direction of ivect and stores it in z_vect;\na unit vector perpendicular to that in the xy plane,  stored in x_vect;\nand a unit vector perpendicular to both,  stored in y_vect.   */\n\nstatic double make_orthogonal_basis( const double *ivect,\n              double *x_vect, double *y_vect, double *z_vect)\n{\n   double rval, len;\n\n   memcpy( z_vect, ivect, 3 * sizeof( double));\n   rval = normalize_vect3( z_vect);\n   len = hypot( z_vect[0], z_vect[1]);\n   x_vect[0] = z_vect[1] / len;\n   x_vect[1] = -z_vect[0] / len;\n   x_vect[2] = 0.;\n   vector_cross_product( y_vect, z_vect, x_vect);\n   return( rval);\n}\n\n/* 'obs_pos' = observer position relative to the geocenter, km\n   'topo_posn' = artsat position relative to the observer, km\n   'sat_vel' = artsat vel, km/minute (usual,  though somewhat oddball,  SxPx units)\n   '*motion_pa' = returned posn angle of motion,  degrees\n   apparent total angular motion is returned               */\n\nstatic double compute_angular_rates( const double *obs_pos, const double *topo_posn,\n              const double *sat_vel, double *motion_pa,\n              double *ra_motion, double *dec_motion)\n{\n   double vel[3];         /* velocity of sat relative to observer */\n   double x_vect[3];    /* unit vector in equatorial plane perpendicular to topo_posn */\n   double y_vect[3];    /* unit vector perpendicular to topo_posn & x_vect */\n   double z_vect[3];    /* unit vector version of topo_posn */\n   double xmotion, ymotion, total_motion, dist;\n   const double omega_E = 1.00273790934;\n                   /* Earth rotations per sidereal day (non-constant) */\n   const double omega = omega_E * 2. * PI / minutes_per_day;\n                   /* Earth rotational rate in radians/minute */\n\n   vel[0] = sat_vel[0] + omega * obs_pos[1];\n   vel[1] = sat_vel[1] - omega * obs_pos[0];\n   vel[2] = sat_vel[2];\n   dist = make_orthogonal_basis( topo_posn, x_vect, y_vect, z_vect);\n   xmotion = dot_product( vel, x_vect) / dist;      /* all in radians/minute */\n   ymotion = dot_product( vel, y_vect) / dist;\n   total_motion = hypot( xmotion, ymotion);\n   *motion_pa = PI + atan2( xmotion, ymotion);\n   *motion_pa *= 180. / PI;\n   *ra_motion = -xmotion * (180. / PI) * 60.;\n   *dec_motion = -ymotion * (180. / PI) * 60.;\n   return( total_motion * (180. / PI) * 60.);      /* cvt to arcmin/min = arcsec/sec */\n}\n\nstatic char _header[200];\nstatic int motion_units = 1;     /* default to '/minute = degrees/hr = \"/sec */\nstatic bool show_separate_motions = false;\nstatic bool output_state_vectors = false;\nstatic bool output_mjd = false;\n\nstatic int show_ephems_from( const char *path_to_tles, const ephem_t *e,\n                                  const char *filename, int start_line)\n{\n   gzFile ifile;\n   char line0[100], line1[100], line2[100];\n   int show_it = 1, header_shown = 0;\n   double jd_tle = 0., tle_range = 1e+10, abs_mag = 0.;\n   const bool is_geocentric = (e->rho_sin_phi == 0. && e->rho_cos_phi == 0.);\n   static const char *header_text =\n           \"Date (UTC)  Time       R.A. (J2000)  decl   Azim   Alt  Elong\"\n           \"  LuElo  Dist(km) \\\"/sec     PA\";\n   static const char *geo_header_text =\n           \"Date (UTC)  Time       R.A. (J2000)  decl   Elong  LuElo  Dist(km) \\\"/sec     PA\";\n\n   if( verbose)\n      printf( \"Should examine '%s'; start line %d\\n\", filename, start_line);\n   snprintf( line0, sizeof( line0), \"%s/%s\", path_to_tles, filename);\n   ifile = gzopen( line0, \"rb\");\n   if( !ifile)       /* maybe it's compressed */\n      {\n      strlcat_error( line0, \".gz\");\n      ifile = gzopen( line0, \"rb\");\n      }\n   if( !ifile)\n      {\n      fprintf( stderr, \"'%s' not opened\\n\", line0);\n      exit( 0);\n      }\n   *line0 = *line1 = '\\0';\n   while( gzgets_trimmed( line2, sizeof( line2), ifile))\n      {\n      tle_t tle;\n\n      if( *line2 == '#')\n         {\n         char *tptr = strstr( line2, \" H \");\n\n         if( !memcmp( line2, \"# MJD \", 6))\n            {\n            jd_tle = atof( line2 + 6) + 2400000.5;\n            tle_range = 1.;\n            show_it = (jd_tle < e->jd_end && jd_tle + tle_range > e->jd_start);\n            }\n         else if( tptr)\n            {\n            abs_mag = atof( tptr + 2);\n            if( verbose)\n               printf( \"H = %.3f\\n\", abs_mag);\n            }\n         }\n      else if( show_it && parse_elements( line1, line2, &tle) >= 0\n                     && desig_match( &tle, e->desig))\n         {\n         double sat_params[N_SAT_PARAMS], jd = e->jd_start;\n         size_t i, j;\n         const int is_deep_type = select_ephemeris( &tle);\n\n         if( is_deep_type)\n            SDP4_init( sat_params, &tle);\n         else\n            SGP4_init( sat_params, &tle);\n         if( verbose > 1)\n            {\n            printf( \"Got TLEs for %f :\\n\", jd);\n            printf( \"%s\\n%s\\n%s\\n\", line0, line1, line2);\n            }\n         for( i = 0; i < (size_t)e->n_steps; i++,\n                                         jd = e->jd_start + (double)i * e->step_size)\n            if( (int)i >= start_line && jd >= jd_tle && jd < jd_tle + tle_range)\n               {\n               char buff[90], dec_buff[20], ra_buff[20], alt_buff[17];\n               double pos[3], vel[3], obs_pos[3], ra, dec, dist;\n               const double t_since = (jd - tle.epoch) * minutes_per_day;\n               double solar_xyzr[4], lunar_xyzr[4], topo_posn[3], elong;\n               double motion_rate, motion_pa;\n               double ra_motion, dec_motion;\n               const char *format_string;\n\n               if( !header_shown)\n                  {\n                  char *tptr;\n\n                  header_shown = 1;\n                  printf( \"\\nEphemerides for %05d = %s%.2s-%s\\n\",\n                              tle.norad_number,\n                              (atoi( tle.intl_desig) > 57000) ? \"19\" : \"20\",\n                              tle.intl_desig, tle.intl_desig + 2);\n                  tptr = line0;\n                  if( *tptr == '0' && tptr[1] == ' ')\n                     tptr += 2;    /* actually a '3LE' with name prefaced by '0 ' */\n                  snprintf( _header, sizeof( _header),\n                          \"%s\\n%s\", tptr, (is_geocentric ? geo_header_text : header_text));\n                  if( show_separate_motions)\n                     strcat( _header, \"    RA \\\"/sec  dec\");\n                  if( motion_units == 60)\n                     while( NULL != (tptr = strstr( _header, \"/sec \")))\n                        memcpy( tptr, \"/min\", 4);\n                  strcat( _header, abs_mag ? \"      Mag\\n\" : \"\\n\");\n                  if( output_state_vectors)\n                     strcpy( _header, \"Date (UTC)  Time\"\n                               \"          x            y            z\"\n                               \"          vx           vy           vz\\n\");\n                  printf( \"%s\", _header);\n                  }\n               if( output_mjd)\n                  snprintf( buff, sizeof( buff), \"%.5f\", jd - 2400000.5);\n               else\n                  full_ctime( buff, jd, FULL_CTIME_YMD | FULL_CTIME_MONTHS_AS_DIGITS\n                                 | FULL_CTIME_LEADING_ZEROES);\n               if( is_deep_type)\n                  SDP4( t_since, &tle, sat_params, pos, vel);\n               else\n                  SGP4( t_since, &tle, sat_params, pos, vel);\n               observer_cartesian_coords( jd, e->lon, e->rho_cos_phi,\n                                        e->rho_sin_phi, obs_pos);\n               get_satellite_ra_dec_delta( obs_pos, pos, &ra, &dec, &dist);\n               epoch_of_date_to_j2000( jd, &ra, &dec);\n               if( output_state_vectors)\n                  {\n                  const double year = 2000. + (jd - 2451545.) / 365.25;\n                  double matrix[9];\n\n                  setup_precession( matrix, year, 2000.);\n                  precess_vector( matrix, pos, pos);\n                  precess_vector( matrix, vel, vel);\n                  printf( \"%s %14.5f%14.5f%14.5f%11.5f%11.5f%11.5f\\n\",\n                              buff, pos[0], pos[1], pos[2],\n                              vel[0] / 60., vel[1] / 60., vel[2] / 60.);\n                  }\n               ra += ra_offset;\n               dec += dec_offset;\n               put_ra_in_buff( ra_buff, ra);\n               put_dec_in_buff( dec_buff, dec);\n               ra_buff[10] = dec_buff[9] = '\\0';\n               for( j = 0; j < 3; j++)\n                  topo_posn[j] = pos[j] - obs_pos[j];\n               motion_rate = compute_angular_rates( obs_pos, topo_posn, vel, &motion_pa,\n                           &ra_motion, &dec_motion);\n               lunar_solar_position( jd, lunar_xyzr, solar_xyzr);\n               ecliptic_to_equatorial( solar_xyzr);\n               ecliptic_to_equatorial( lunar_xyzr);\n               if( !is_geocentric)\n                  {\n                  double x_vect[3], y_vect[3], z_vect[3], alt, az;\n\n                  make_orthogonal_basis( obs_pos, x_vect, y_vect, z_vect);\n                  az = PI + atan2( dot_product( x_vect, topo_posn),\n                                   dot_product( y_vect, topo_posn));\n                  az *= 180. / PI;\n                  alt = 90. - angle_between( topo_posn, obs_pos);\n                  snprintf( alt_buff, sizeof( alt_buff), \" %5.1f %+05.1f\",\n                              az,  alt);\n                  }\n               else\n                  *alt_buff = '\\0';\n               elong = angle_between( topo_posn, solar_xyzr);\n               if( !output_state_vectors)\n                  {\n                  printf( \"%s  %s  %s%s %6.1f %6.1f %8.0f\", buff, ra_buff, dec_buff,\n                     alt_buff, elong, angle_between( topo_posn, lunar_xyzr), dist);\n                  motion_rate *= (double)motion_units;\n                  if( motion_rate < 9.999)\n                     format_string = \"  %6.4f %6.1f\";\n                  else if( motion_rate < 99.99)\n                     format_string = \"  %6.3f %6.1f\";\n                  else if( motion_rate < 999.9)\n                     format_string = \"  %6.2f %6.1f\";\n                  else if( motion_rate < 9999.)\n                     format_string = \"  %6.1f %6.1f\";\n                  else\n                     format_string = \"  %6.0f %6.1f\";\n                  printf( format_string, motion_rate, motion_pa);\n                  if( show_separate_motions)\n                     {\n                     const char precision = format_string[5];\n\n                     snprintf( buff, sizeof( buff),\n                                 \"  %%+7.%cf %%+7.%cf\", precision, precision);\n                     printf( buff, ra_motion * (double)motion_units,\n                                  dec_motion * (double)motion_units);\n                     }\n\n                  if( !abs_mag)\n                     printf( \"\\n\");\n                  else\n                     {\n                     const double phase_ang = (180. - elong) * (PI / 180.);\n                     double mag = abs_mag + 5. * log10( dist / AU_IN_KM)\n                              + phase_angle_correction_to_magnitude(\n                                       phase_ang, 0.15);\n                     printf( \"%8.1f\\n\", mag);\n                     }\n                  }\n               start_line = (int)i + 1;\n               }\n         }\n      strcpy( line0, line1);\n      strcpy( line1, line2);\n      }\n   gzclose( ifile);\n   return( start_line);\n}\n\nstatic const char *tle_list_filename = \"tle_list.txt\";\n\nint generate_artsat_ephems( const char *path_to_tles, const ephem_t *e)\n{\n   gzFile ifile;\n   char buff[100];\n   int is_in_range = 0, id_matches = 1, start_line = 0;\n\n   snprintf( buff, sizeof( buff), \"%s/%s\", path_to_tles, tle_list_filename);\n   if( verbose > 1)\n      printf( \"Opening '%s', looking for '%s'\\n\", buff, e->desig);\n   ifile = gzopen( buff, \"rb\");\n   if( !ifile)\n      {\n      strlcat_error( buff, \".gz\");\n      ifile = gzopen( buff, \"rb\");\n      }\n   if( !ifile)\n      {\n      fprintf( stderr, \"'%s' not opened\\n\", buff);\n      exit( 0);\n      }\n   while( start_line != e->n_steps &&\n                          gzgets_trimmed( buff, sizeof( buff), ifile))\n      {\n      if( !memcmp( buff, \"# Range:\", 8))\n         {\n         char t_start[40], t_end[40];\n         int n_scanned = sscanf( buff + 8, \"%39s %39s\", t_start, t_end);\n\n         assert( 2 == n_scanned);\n         if( get_time_from_string( 0., t_start, FULL_CTIME_YMD, NULL) < e->jd_end\n                  && get_time_from_string( 0., t_end, FULL_CTIME_YMD, NULL) > e->jd_start)\n            is_in_range = 1;\n         }\n      if( !memcmp( buff, \"# ID:\", 5))\n         {\n         int i;\n\n         if( buff[5] != ' ' || buff[11] != ' ' || buff[12] != ' ')\n            fprintf( stderr, \"BAD LINE %s\\n\", buff);\n         for( i = 6; i < 10; i++)\n            if( !isdigit( buff[i]) || !isdigit( buff[i + 7]))\n               {\n               printf( \"BAD LINE (2) %s\\n\", buff);\n               i = 99;\n               }\n         if( strcmp( e->desig, buff + 13) && atoi( buff + 5) != atoi( e->desig))\n            id_matches = 0;\n         }\n      if( !memcmp( buff, \"# Include \", 10))\n         {\n         if( is_in_range && id_matches)\n            start_line = show_ephems_from( path_to_tles, e, buff + 10, start_line);\n         is_in_range = 0;\n         id_matches = 1;\n         }\n      }\n   gzclose( ifile);\n   if( start_line)\n      printf( \"%s\", _header);\n   return( start_line);\n}\n\nstatic int set_location( ephem_t *e, const char *mpc_code, const char *obscode_file_name)\n{\n   mpc_code_t c;\n   int rval = get_lat_lon_info( &c, mpc_code);\n\n   if( rval)\n      {\n      gzFile ifile = gzopen( obscode_file_name, \"rb\");\n      char buff[200];\n\n      if( !ifile)\n         {\n         fprintf( stderr, \"'%s' not found\\n\", obscode_file_name);\n         exit( 0);\n         }\n      while( rval && gzgets_trimmed( buff, sizeof( buff), ifile))\n         if( !memcmp( mpc_code, buff, 3))\n            {\n            const int planet = get_mpc_code_info( &c, buff);\n\n            if( planet != 3)\n               {\n               fprintf( stderr, \"MPC code '%s' is for planet %d\\n\",\n                           mpc_code, planet);\n               exit( 0);\n               }\n            rval = 0;\n            printf( \"%s\\n\", c.name);\n            }\n      gzclose( ifile);\n      }\n   if( !rval)\n      {\n      e->lat = c.lat;\n      e->lon = c.lon;\n      e->alt = c.alt;\n      e->rho_cos_phi = c.rho_cos_phi;\n      e->rho_sin_phi = c.rho_sin_phi;\n      if( c.lon > PI)\n         c.lon -= PI + PI;\n      if( c.rho_sin_phi || c.rho_cos_phi)\n         printf( \"Latitude %c %f, Longitude %c %f\\nAltitude %.1f meters (above WGS84 ellipsoid)\\n\",\n                  (c.lat > 0. ? 'N' : 'S'), fabs( c.lat) * 180. / PI,\n                  (c.lon > 0. ? 'E' : 'W'), fabs( c.lon) * 180. / PI,\n                  c.alt);\n      }\n   return( rval);\n}\n\n#ifdef ON_LINE_VERSION\n   #define OBSCODES_DOT_HTML_FILENAME  \"/home/projectp/public_html/cgi-bin/fo/ObsCodes.htm\"\n   #define ROVERS_DOT_TXT_FILENAME     \"/home/projectp/public_html/cgi-bin/fo/rovers.txt\"\n   #define PATH_TO_TLES                \"/home/projectp/public_html/tles\"\n#else\n   #define OBSCODES_DOT_HTML_FILENAME  \"/home/phred/.find_orb/ObsCodes.htm\"\n   #define ROVERS_DOT_TXT_FILENAME     \"/home/phred/.find_orb/rovers.txt\"\n   #define PATH_TO_TLES                \"/home/phred/tles\"\n#endif\n\nstatic const char *get_arg( const char **argv)\n{\n   const char *rval;\n\n   if( argv[0][0] == '-' && argv[0][1])\n      {\n      if( !argv[0][2] && argv[1])\n         rval = argv[1];\n      else\n         rval = argv[0] + 2;\n      }\n   else\n      rval = NULL;\n   if( !rval)\n      {\n      fprintf( stderr, \"Can't get an argument : '%s'\\n\", argv[0]);\n      exit( 0);\n      }\n   return( rval);\n}\n\nstatic void fix_desig( char *desig)\n{\n   size_t i;\n   int bitmask = 0;\n\n   for( i = 0; i < 10 && desig[i]; i++)\n      if( isdigit( desig[i]))\n         bitmask |= (1 << (int)i);\n   if( i >= 9 && bitmask == 0xef && desig[4] == '-')\n      {\n      desig[0] = desig[2];                   /* it's in YYYY-NNNA form; */\n      desig[1] = desig[3];                   /* cvt to YYNNNA form */\n      for( i = 5; desig[i - 1]; i++)\n         desig[i - 3] = desig[i];\n      }\n}\n\nstatic void error_help( void)\n{\n   printf( \"'sat_eph' arguments:\\n\"\n           \"   -c(MPC code) : specify location (default = geocentric)\\n\"\n           \"   -t(date/time) : starting time of ephemeris (default = now)\\n\"\n           \"   -n(#) : number of ephemeris steps (default = 20)\\n\"\n           \"   -s(#) : ephemeris step size in days (default = 1h)\\n\"\n           \"   -S    : show motions in RA/dec components,  as well as total/PA\\n\");\n   printf( \"   -o(#) : five digit NORAD number or YYNNNA international designation\\n\"\n           \"   -r    : do _not_ round times to nearest step size\\n\"\n           \"   -u    : show motions in \\\"/min = degrees/hr (default is \\\"/sec)\\n\"\n           \"   -m    : show times as MJD\\n\"\n           \"   -V    : output state vectors instead of observables\\n\"\n           \"   -v(#) : level of verbosity\\n\");\n}\n\nint dummy_main( const int argc, const char **argv)\n{\n   int i;\n   ephem_t e;\n   bool round_to_nearest_step = true;\n   const char *mpc_code = \"500\";\n   const char *override_tle_filename = NULL;\n\n   if( argc < 2)\n      {\n      error_help( );\n      return( 0);\n      }\n   memset( &e, 0, sizeof( ephem_t));\n   e.jd_start = current_jd( );\n   e.n_steps = 20;\n   e.step_size = 1. / 24.;\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-' && argv[i][1])\n         {\n         const char *arg = get_arg( argv + i);\n\n         switch( argv[i][1])\n            {\n            case 'c':\n               mpc_code = arg;\n               break;\n            case 'f':\n               tle_list_filename = arg;\n               break;\n            case 'F':\n               override_tle_filename = arg;\n               break;\n            case 't':\n               e.jd_start = get_time_from_string( e.jd_start, arg, FULL_CTIME_YMD, NULL);\n               break;\n            case 'm':\n               output_mjd = true;\n               break;\n            case 'n':\n               e.n_steps = atoi( arg);\n               break;\n            case 'O':\n               if( sscanf( arg, \"%lf,%lf\", &ra_offset, &dec_offset) == 2)\n                  {\n                  printf( \"Offsetting by %f degrees in RA,  %f degrees in dec\\n\",\n                                 ra_offset, dec_offset);\n                  ra_offset *= PI / 180.;\n                  dec_offset *= PI / 180.;\n                  }\n               break;\n            case 'r':\n               round_to_nearest_step = false;\n               break;\n            case 's':\n               if( arg && *arg)\n                  {\n                  const char end_char = arg[strlen( arg) - 1];\n\n                  e.step_size = atof( arg);\n                  switch( end_char)\n                     {\n                     case 'h':\n                        e.step_size /= hours_per_day;\n                        break;\n                     case 'm':\n                        e.step_size /= minutes_per_day;\n                        break;\n                     case 's':\n                        e.step_size /= seconds_per_day;\n                        break;\n                     }\n                  }\n               break;\n            case 'S':\n               show_separate_motions = true;\n               break;\n            case 'u':\n               motion_units = 60;\n               break;\n            case 'o':\n               /* Will handle below */\n               break;\n            case 'v':\n               verbose = 1 + atoi( arg);\n               break;\n            case 'V':\n               output_state_vectors = 1;\n               break;\n            default:\n               fprintf( stderr, \"Unrecognized option '%s'\\n\", argv[i]);\n               error_help( );\n               return( 0);\n            }\n         }\n   if( set_location( &e, mpc_code, OBSCODES_DOT_HTML_FILENAME))\n      if( set_location( &e, mpc_code, ROVERS_DOT_TXT_FILENAME))\n         fprintf( stderr, \"WARNING: Could not parse location '%s'\\n\", mpc_code);\n   if( round_to_nearest_step && e.step_size)\n      e.jd_start = floor( (e.jd_start - 0.5) / e.step_size) * e.step_size + 0.5;\n   e.jd_end   = e.jd_start + (double)e.n_steps * e.step_size;\n   if( verbose)\n      printf( \"arguments parsed;  JDs %f to %f\\n\", e.jd_start, e.jd_end);\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-' && argv[i][1] == 'o')\n         {\n         char desig[30];\n\n         strncpy( desig, get_arg( argv + i), 29);\n         fix_desig( desig);\n         e.desig = desig;\n         if( override_tle_filename)\n            show_ephems_from( PATH_TO_TLES, &e, override_tle_filename, 0);\n         else\n            generate_artsat_ephems( PATH_TO_TLES, &e);\n         }\n   return( 0);\n}\n\n#ifndef ON_LINE_VERSION\nint main( const int argc, const char **argv)\n{\n   return( dummy_main( argc, argv));\n}\n#else\n\n#include <errno.h>\n#ifdef __has_include\n   #if __has_include(<cgi_func.h>)\n       #include \"cgi_func.h\"\n   #else\n       #error   \\\n         'cgi_func.h' not found.  This project depends on the 'lunar'\\\n         library.  See www.github.com/Bill-Gray/lunar .\\\n         Clone that repository,  'make'  and 'make install' it.\n   #endif\n#else\n   #include \"cgi_func.h\"\n#endif\n\nint main( const int unused_argc, const char **unused_argv)\n{\n   const char *argv[2000];\n   const size_t max_buff_size = 40000;       /* room for 500 obs */\n   char *buff = (char *)malloc( max_buff_size);\n   char field[30], time_text[80];\n   char num_steps[30], step_size[30], obs_code[40];\n   FILE *lock_file = fopen( \"lock.txt\", \"w\");\n   int cgi_status, i, argc = 9;\n   bool round_step = false;\n#ifndef _WIN32\n   extern char **environ;\n\n   avoid_runaway_process( 15);\n#endif         /* _WIN32 */\n   setbuf( lock_file, NULL);\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argc);\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argv);\n   printf( \"Content-type: text/html\\n\\n\");\n   printf( \"<html> <body> <pre>\\n\");\n   if( !lock_file)\n      {\n      printf( \"<p> Server is busy.  Try again in a minute or two. </p>\");\n      printf( \"<p> Your artsat ephemerides are very important to us! </p>\");\n      return( 0);\n      }\n   fprintf( lock_file, \"We're in\\n\");\n   *time_text = *num_steps = *step_size = *obs_code = '\\0';\n#ifndef _WIN32\n   for( i = 0; environ[i]; i++)\n      fprintf( lock_file, \"%s\\n\", environ[i]);\n#endif\n   cgi_status = initialize_cgi_reading( );\n   fprintf( lock_file, \"CGI status %d\\n\", cgi_status);\n   if( cgi_status <= 0)\n      {\n      printf( \"<p> <b> CGI data reading failed : error %d </b>\", cgi_status);\n      printf( \"This isn't supposed to happen.</p>\\n\");\n      return( 0);\n      }\n   while( !get_cgi_data( field, buff, NULL, max_buff_size))\n      {\n      fprintf( lock_file, \"Field '%s'\\n\", field);\n      if( !strcmp( field, \"time\") && strlen( buff) < sizeof( time_text))\n         strcpy( time_text, buff);\n      if( !strcmp( field, \"obj_name\"))\n         {\n         char *obj_name = (char *)malloc( strlen( buff) + 1);\n\n         strcpy( obj_name, buff);\n         argv[argc++] = \"-o\";\n         argv[argc++] = obj_name;\n         }\n      else if( !memcmp( field, \"obj_\", 4))      /* selected an object check-box */\n         {\n         char *obj_name = (char *)malloc( strlen( field) - 3);\n\n         strcpy( obj_name, field + 4);\n         argv[argc++] = \"-o\";\n         argv[argc++] = obj_name;\n         }\n      else if( !strcmp( field, \"motion60\"))\n         motion_units = 60;\n      if( !strcmp( field, \"num_steps\") && strlen( buff) < sizeof( num_steps))\n         strcpy( num_steps, buff);\n      if( !strcmp( field, \"step_size\") && strlen( buff) < sizeof( step_size))\n         {\n         char *tptr = strchr( buff, 'v');\n\n         if( tptr)\n            {\n            verbose = atoi( tptr + 1);\n            *tptr = '\\0';\n            }\n         strcpy( step_size, buff);\n         }\n      if( !strcmp( field, \"obs_code\") && strlen( buff) < sizeof( obs_code))\n         strcpy( obs_code, buff);\n      if( !strcmp( field, \"round_step\"))\n         round_step = true;\n      if( !strcmp( field, \"vectors\"))\n         output_state_vectors = true;\n      if( !strcmp( field, \"mjd\"))\n         output_mjd = true;\n      if( !strcmp( field, \"show_separate_motions\"))\n         argv[argc++] = \"-S\";\n      }\n   fprintf( lock_file, \"Fields read\\n\");\n   if( !round_step)\n      argv[argc++] = \"-r\";\n   argv[0] = \"sat_eph\";\n   argv[1] = \"-t\";\n   argv[2] = time_text;\n   argv[3] = \"-c\";\n   argv[4] = obs_code;\n   argv[5] = \"-n\";\n   argv[6] = num_steps;\n   argv[7] = \"-s\";\n   argv[8] = step_size;\n   argv[argc] = NULL;\n   for( i = 0; argv[i]; i++)\n      fprintf( lock_file, \"arg %d: '%s'\\n\", (int)i, argv[i]);\n   dummy_main( argc, argv);\n   fprintf( lock_file, \"dummy_main called\\n\");\n   free( buff);\n   printf( \"On-line Sat_eph compiled \" __DATE__ \" \" __TIME__ \" UTC-5h\\n\");\n   printf( \"See <a href='https://www.github.com/Bill-Gray/sat_code'>\"\n               \"https://www.github.com/Bill-Gray/sat_code</a> for source code\\n\");\n   printf( \"</pre> </body> </html>\");\n   return( 0);\n}\n#endif\n"
  },
  {
    "path": "sat_id.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n    sat_id.cpp     8 March 2003,  with updates as listed below\n\n   An example 'main' function illustrating how to find which satellite(s)\nare within a given radius of a given RA/dec,  as seen from a given\npoint.  The code reads in a file of observations in MPC format (name\nprovided as the first command-line argument).  For example:\n\nsat_id mpc_obs.txt\n\n   would hunt through the file 'mpc_obs.txt' for MPC-formatted\nobservations.  It would then read the file 'alldat.tle',  looking\nfor corresponding satellites within .2 degrees of said observations.\nIt then spits out the original file,  with satellite IDs added\n(when found) after each observation line.  For each IDed satellite,\nthe international and NORAD designations are given,  along with\nits angular distance from the search point,  position angle of\nmotion,  and apparent angular rate of motion in arcminutes/second\n(or,  equivalently,  degrees/minute). */\n\n/* 2 July 2003:  fixed the day/month/year to JD part of 'get_mpc_data()'\nso it will work for all years >= 0 (previously,  it worked for years\n2000 to 2099... plenty for the practical purpose of ID'ing recently-found\nsatellites,  but this is also an 'example' program.) */\n\n/* 3 July 2005:  revised the check on the return value for parse_elements().\nNow elements with bad checksums won't be rejected. */\n\n/* 23 June 2006:  after comment from Eric Christensen,  revised to use\nnames 'ObsCodes.html' or 'ObsCodes.htm',  with 'stations.txt' being a\nthird choice.  Also added the '-a' command line switch to cause the program\nto show all lines from input (default is now that only MPC astrometric\ninput gets echoed.)   */\n\n/* 30 June 2006:  further comment from Eric Christensen:  when computing\nobject motion from two consecutive observations,  if the second one has\na date/time preceding the first,  you get a negative rate of motion that's\noff by 180 degrees.  Fixed this. */\n\n/* 17 Nov 2006:  artificial satellite data is now being provided in a\nfile named 'ALL_TLE.TXT'.  I've modified the default TLE to match.\n(Note : since modified to be read from 'tle_list.txt',  as found in\nthe https://www.github.com/Bill-Gray/tles repository.  This allows\nfor multiple TLEs to be read.) */\n\n/* 22 Oct 2012:  minor cosmetic changes,  such as making constant variables\nof type 'const',  updating URL for the MPC station code file,  adding a\ncomment or two. */\n\n/* 7 Jan 2013:  revised output to show satellite name if available,  plus\nthe eccentricity,  orbital period,  and inclination. */\n\n/* 2013 Dec 8:  revised to pay attention to \"# MJD\" and \"#Ephem start\"\nlines,  for files that contain many TLEs covering different time spans\nfor the same object.  I sometimes create such files;  when that happens,\nfor each observation,  only the TLE(s) covering that observation's time\nshould be used,  and the others are suppressed.       */\n\n#include <stdio.h>\n#include <string.h>\n#include <math.h>\n#include <time.h>\n#include <ctype.h>\n#include <stdlib.h>\n#include <assert.h>\n#if defined( _WIN32) || defined( __WATCOMC__)\n   #include <malloc.h>     /* for alloca() prototype */\n   #include \"zlibstub.h\"\n#else\n   #include <unistd.h>\n   #include <zlib.h>\n#endif\n\n#if defined(_MSC_VER) && _MSC_VER < 1900\n                      /* For older MSVCs,  we have to supply our own  */\n                      /* snprintf().  See snprintf.cpp for details.  */\nint snprintf( char *string, const size_t max_len, const char *format, ...);\n#endif\n\n#if defined( __has_include)\n   #if !__has_include(<watdefs.h>)\n      #error   \\\n        \"You need the 'lunar' library (https://www.github.com/Bill-Gray/lunar).\\\n See https://github.com/Bill-Gray/sat_code/issues/2 for a fix to this.\"\n            /* This would normally be followed by dozens of errors.  GCC, */\n            /* at least,  stops completely when it can't find a system */\n            /* include file.  */\n      #include <stop_cascading_errors>\n   #endif\n#endif\n\n#include \"norad.h\"\n#include \"observe.h\"\n#include \"watdefs.h\"\n#include \"mpc_func.h\"\n#include \"afuncs.h\"\n#include \"date.h\"\n#include \"sat_util.h\"\n#include \"stringex.h\"\n\n#define OBSERVATION struct observation\n\nOBSERVATION\n   {\n   char text[81];\n   double jd, ra, dec;\n   double observer_loc[3];\n   };\n\ntypedef struct\n{\n   double dist, ra, dec, motion_rate, motion_pa;\n   int norad_number;\n   char intl_desig[9];\n   char text[80];\n   bool in_shadow;\n} match_t;\n\ntypedef struct\n{\n   OBSERVATION *obs;\n   size_t idx1, idx2, n_obs, n_matches;\n   double speed;\n   match_t *matches;\n} object_t;\n\n/* When we encounter a line for a spacecraft-based observation's offset\nfrom the geocenter (a \"second line\" in the MPC's punched-card format system),\nwe store that offset in an offset_t structure.  Once the file has been\nread in,  we go back and match the offsets to their corresponding observations.\nIf the lines aren't in proper order (which happens),  we'll fix it,  and\ncan detect errors where we get one line but not the other. */\n\ntypedef struct\n{\n   double jd, posn[3];\n   char mpc_code[4];\n} offset_t;\n\n#define PI 3.1415926535897932384626433832795028841971693993751058209749445923\n#define TIME_EPSILON (1./86400.)\n#define EARTH_MAJOR_AXIS    6378137.\n#define EARTH_MINOR_AXIS    6356752.\n#define is_power_of_two( X)   (!((X) & ((X) - 1)))\n\n/* By default,  Sat_ID checks for possible matches with both artificial\nobjects (its main use) and some natural irregular satellites of the gas\ngiants.  Use the '-n0' command line option to identify only artificial\nobjects,  or '-n1' to identify only the natural irregular objects. */\n\n#define IDENTIFY_ALL             2\n#define IDENTIFY_ARTSATS         0\n#define IDENTIFY_NATSATS         1\n\nstatic int identify_filter = IDENTIFY_ALL;\n\n/* For testing purposes,  I sometimes want _only_ \"my\" TLEs (not the\n   'classified' or Space-Watch TLEs) to be used.  This is invoked with -s. */\nstatic bool my_tles_only = false;\n\n#if !defined( ON_LINE_VERSION) && !defined( _WIN32)\n   #define CSI \"\\x1b[\"\n   #define OSC \"\\x1b]\"\n   #define REVERSE_VIDEO      CSI \"0m\" CSI \"7m\"\n   #define NORMAL_VIDEO       CSI \"0m\"\n#else\n   #define REVERSE_VIDEO\n   #define NORMAL_VIDEO\n#endif\n\n/* Sputnik 1 was launched on 1957 Oct 4.  There is no point in\nchecking for satellites before that date. TLEs,  and therefore\nthis program,  won't work after 2057 Jan 1. */\n\nconst double oct_4_1957 = 2436115.5;\nconst double jan_1_2057 = 2472364.5;\n\nstatic int get_mpc_data( OBSERVATION *obs, const char *buff)\n{\n   obs->jd = extract_date_from_mpc_report( buff, NULL);\n   if( obs->jd < oct_4_1957 || obs->jd > jan_1_2057)\n      return( -1);            /* not an 80-column MPC record */\n   if( get_ra_dec_from_mpc_report( buff, NULL, &obs->ra, NULL,\n                                     NULL, &obs->dec, NULL))\n      if( 's' != buff[14] && 'v' != buff[14])   /* satellite offsets and */\n         return( -2);          /* roving obs lat/lons won't have RA/decs */\n   assert( strlen( buff) < sizeof( obs->text));\n   strncpy( obs->text, buff, sizeof( obs->text));\n   obs->text[80] = '\\0';\n   return( 0);\n}\n\nstatic int extract_csv_value( char *obuff, const char *ibuff, int idx, size_t obuff_len)\n{\n   while( idx && *ibuff)\n      if( *ibuff++ == ',')\n         idx--;\n   if( !*ibuff)\n      return( -1);\n   if( *ibuff == '\"')\n      ibuff++;\n   obuff_len--;\n   while( obuff_len-- && *ibuff && *ibuff != ',' && *ibuff != '\"')\n      *obuff++ = *ibuff++;\n   *obuff = '\\0';\n   return( 0);\n}\n\n/* If we encounter 'field' data -- we're determining which artsats\nare in a field,  not which ones match moving objects -- the output\nis very different : */\n\nstatic bool field_mode = false;\n\n/* An imaging field is specified as an image name,  date/time, RA and\ndec in decimal degrees,  and MPC obscode,  all comma-separated,  such as\n\nMyImage,2023 nov 17 03:14:15.9,271.818,-14.142,T05\n\"Another image\",2023-11-17.314159,21 16 58.21,\"+31 41 59.26\",\"V00\"\n\nIn the first,  the RA and dec are in decimal degrees;  in the second,\nsexagesimal notation is used and the RA is assumed to be in hours,\nminutes,  and seconds.  You need the commas,  but can skip the\nquotation marks.     */\n\nstatic int get_field_data( OBSERVATION *obs, const char *buff)\n{\n   int rval = -1;\n   size_t n_commas, i;\n\n   for( i = n_commas = 0; buff[i] && n_commas < 5; i++)\n      if( buff[i] == ',')\n         n_commas++;\n   if( n_commas == 4 && strlen( buff) < 80)\n      {\n      char tbuff[80];\n      int bytes_read;\n\n      extract_csv_value( tbuff, buff, 1, sizeof( tbuff));\n      obs->jd = get_time_from_string( 0., tbuff, 0, NULL);\n      if( obs->jd > oct_4_1957 && obs->jd < jan_1_2057)\n         {\n         extract_csv_value( tbuff, buff, 4, sizeof( tbuff));\n         if( strlen( tbuff) != 3)     /* MPC code must be three characters */\n            return( -1);\n         strcpy( obs->text + 77, tbuff);\n         extract_csv_value( tbuff, buff, 2, sizeof( tbuff));\n         obs->ra = get_ra_from_string( tbuff, &bytes_read);\n         if( bytes_read <= 0)\n            return( -1);\n         extract_csv_value( tbuff, buff, 3, sizeof( tbuff));\n         obs->dec = get_dec_from_string( tbuff, &bytes_read);\n         if( bytes_read <= 0)\n            return( -1);\n         extract_csv_value( obs->text, buff, 0, sizeof( obs->text));\n         field_mode = true;\n         rval = 0;\n         }\n      }\n   return( rval);\n}\n\n/* This loads up the file 'ObsCodes.html' into memory on its first call.\nThen,  given an MPC code,  it finds the corresponding line and copies\nit into 'station_code_data'.  It looks in several places for the file;\nif you've installed Find_Orb,  it should be able to get it from the\n~/.find_orb directory.  It also checks for the truncated 'ObsCodes.htm'\nversion of the file.      */\n\nint verbose = 0;\nstatic bool check_all_tles = false;\n\nstatic int get_station_code_data( char *station_code_data,\n                  const char *mpc_code)\n{\n   static char *cached_data, *cached_ptr;\n\n   if( !mpc_code)       /* freeing memory */\n      {\n      if( cached_data)\n         free( cached_data);\n      cached_data = cached_ptr = NULL;\n      return( 0);\n      }\n   *station_code_data = '\\0';\n   if( !cached_data)\n      {\n      const char *filenames[2] = { \"ObsCodes.html\", \"ObsCodes.htm\" };\n      FILE *ifile = NULL;\n      size_t size;\n      int i;\n\n      for( i = 0; !ifile && i < 2; i++)\n         ifile = local_then_config_fopen( filenames[i], \"rb\");\n      if( !ifile)\n         {\n         printf( \"Failed to find MPC station list 'ObsCodes.html'\\n\");\n         printf( \"This can be downloaded at:\\n\\n\");\n         printf( \"http://www.minorplanetcenter.org/iau/lists/ObsCodes.html\\n\");\n         exit( -3);\n         }\n      fseek( ifile, 0L, SEEK_END);\n      size = (size_t)ftell( ifile);\n      fseek( ifile, 0L, SEEK_SET);\n      cached_data = (char *)malloc( size + 1);\n      if( fread( cached_data, 1, size, ifile) != size)\n         {\n         printf( \"Failed to read station file\\n\");\n         exit( -4);\n         }\n      fclose( ifile);\n      if( verbose)\n         printf( \"Station codes: %u bytes read\\n\", (unsigned)size);\n      ifile = local_then_config_fopen( \"rovers.txt\", \"rb\");\n      if( ifile)\n         {                             /* if 'rovers.txt' is available, */\n         size_t size2;               /* append its data to ObsCodes.htm */\n\n         fseek( ifile, 0L, SEEK_END);\n         size2 = (size_t)ftell( ifile);\n         fseek( ifile, 0L, SEEK_SET);\n         cached_data = (char *)realloc( cached_data, size + size2 + 1);\n         if( fread( cached_data + size, 1, size2, ifile) != size2)\n            {\n            printf( \"Failed to read 'rovers.txt'\\n\");\n            exit( -4);\n            }\n         fclose( ifile);\n         size += size2;\n         }\n      cached_data[size] = '\\0';\n      }\n   if( !cached_ptr || memcmp( cached_ptr, mpc_code, 3))\n      {\n      char search_buff[5];\n\n      snprintf_err( search_buff, sizeof( search_buff), \"\\n%.3s\", mpc_code);\n      cached_ptr = strstr( cached_data, search_buff);\n      if( cached_ptr)\n         cached_ptr++;\n      }\n   if( cached_ptr)\n      {\n      size_t i;\n\n      for( i = 0; cached_ptr[i] >= ' '; i++)\n         station_code_data[i] = cached_ptr[i];\n      station_code_data[i] = '\\0';\n      }\n   else\n      {\n      static char *codes_already_reported = NULL;\n      static size_t n_reported = 0;\n\n      if( codes_already_reported && strstr( codes_already_reported, mpc_code))\n         return( -1);      /* we've already reported this as 'unknown' */\n      n_reported++;\n      codes_already_reported = (char *)realloc( codes_already_reported,\n                        n_reported * 4 + 1);\n      if( n_reported == 1)    /* realloc( ) doesn't initialize to zero */\n         *codes_already_reported = '\\0';\n      strcat( codes_already_reported, mpc_code);\n      strcat( codes_already_reported, \" \");\n      printf( \"Station code '%s' not found.\\n\", mpc_code);\n      if( n_reported == 1)    /* only really need the following text once */\n         {\n#ifdef ON_LINE_VERSION\n         printf( \"If this is a new MPC code,  it could be that this service needs to be\\n\");\n         printf( \"updated to know about it.  Please contact pluto at projectpluto.com so\\n\");\n         printf( \"I can fix this.\\n\");\n#else\n         printf( \"If this is a new MPC code,  you may need to get this file:\\n\");\n         printf( \"http://www.minorplanetcenter.org/iau/lists/ObsCodes.html\\n\");\n         printf( \"and replace the existing ObsCodes.html.\\n\");\n#endif\n         }\n      }\n   return( cached_ptr ? 0 : -1);\n}\n\n/* Code to check if a 'second line' (v for roving observer or s for\nspacecraft observation) matches a 'first line' (the one that has the actual\nastrometry).  If the date and obscode match,  and the observation doesn't\nalready have a non-geocentric position set,  then we have a match.  */\n\nstatic bool offset_matches_obs( const offset_t *offset, const OBSERVATION *obs)\n{\n   if( offset->jd == obs->jd && !obs->observer_loc[0]\n               && !strcmp( offset->mpc_code, obs->text + 77)\n               && !obs->observer_loc[1] && !obs->observer_loc[2])\n      return( true);\n   else\n      return( false);\n}\n\n/* (XXX) locations are specified with text such as\n\nCOM Long. 239 18 45 E, Lat. 33 54 11 N, Alt. 100m, Google Earth        */\n\nstatic char xxx_location[80];\n\nstatic int set_observer_location( OBSERVATION *obs)\n{\n   mpc_code_t code_data;\n   int rval;\n\n   if( memcmp( obs->text + 77, \"XXX\", 3))\n      {\n      char station_data[100];\n\n      rval = get_station_code_data( station_data, obs->text + 77);\n      if( !rval)\n         get_mpc_code_info( &code_data, station_data);\n      }\n   else\n      {\n      static bool first_time = true;\n\n      rval = get_xxx_location_info( &code_data, xxx_location);\n      if( rval && first_time)\n         printf( \"WARNING: (XXX) observations won't be handled,  because the\\n\"\n               \"observatory location was either missing or incorrectly formatted.\\n\"\n               \"See https://www.projectpluto.com/xxx.htm for information on how\\n\"\n               \"this line should be handled.\\n\");\n      first_time = false;\n      }\n   if( !rval)\n      {\n      observer_cartesian_coords( obs->jd, code_data.lon,\n            code_data.rho_cos_phi, code_data.rho_sin_phi, obs->observer_loc);\n      j2000_to_epoch_of_date( obs->jd, &obs->ra, &obs->dec);\n      }\n   return( rval);\n}\n\n/* Loads up MPC-formatted 80-column observations from a file.  Makes\na pass to find out how many observations there are,  allocates space\nfor them,  then reads them again to actually load the observations. */\n\nstatic const char *_target_desig;\n\nstatic OBSERVATION *get_observations_from_file( FILE *ifile, size_t *n_found,\n         const double t_low, const double t_high)\n{\n   OBSERVATION *rval = NULL, obs;\n   void *ades_context = init_ades2mpc( );\n   char buff[400];\n   size_t count = 0, n_allocated = 0, n_offsets = 0, i;\n   size_t n_obs_without_loc = 0;\n   offset_t *offsets = NULL;\n   int n_errors_found = 0;\n   const clock_t t0 = clock( );\n\n   assert( ades_context);\n   memset( &obs, 0, sizeof( OBSERVATION));\n   while( fgets_with_ades_xlation( buff, sizeof( buff), ades_context, ifile))\n      if( (!get_mpc_data( &obs, buff) || !get_field_data( &obs, buff))\n                   && obs.jd > t_low && obs.jd < t_high\n                   && (!_target_desig || strstr( buff, _target_desig)))\n         {\n         if( buff[14] == 's' || buff[14] == 'v')\n            {                 /* satellite obs or roving observer */\n            offset_t toff;\n\n            toff.jd = obs.jd;\n            strlcpy_error( toff.mpc_code, buff + 77);\n            if( buff[14] == 's')\n               {\n               int err_code = get_satellite_offset( buff, toff.posn);\n               for( i = 0; i < 3; i++)\n                  toff.posn[i] *= AU_IN_KM;\n               ecliptic_to_equatorial( toff.posn);\n               if( err_code < 0 && n_errors_found++ < 10)\n                     fprintf( stderr, REVERSE_VIDEO\n                                    \"Malformed satellite offset; err %d\\n%s\\n\"\n                                    NORMAL_VIDEO, err_code, buff);\n               }\n            else\n               {\n               double lat, lon, alt_in_meters, rho_sin_phi, rho_cos_phi;\n\n               if( sscanf( buff + 34, \"%lf %lf %lf\", &lon, &lat, &alt_in_meters) != 3)\n                  if( n_errors_found++ < 10)\n                     fprintf( stderr, \"Couldn't parse roving observer:\\n%s\\n\", buff);\n               lon *= PI / 180.;\n               lat *= PI / 180.;\n               lat_alt_to_parallax( lat, alt_in_meters,\n                          &rho_cos_phi, &rho_sin_phi,\n                          EARTH_MAJOR_AXIS, EARTH_MINOR_AXIS);\n               observer_cartesian_coords( obs.jd, lon, rho_cos_phi,\n                                        rho_sin_phi, toff.posn);\n               }\n            n_offsets++;\n            offsets = (offset_t *)realloc( offsets, n_offsets * sizeof( offset_t));\n            offsets[n_offsets - 1] = toff;\n            }\n         else if( !set_observer_location( &obs))\n            {\n            if( count == n_allocated)\n               {\n               n_allocated += 10 + n_allocated / 2;\n               rval = (OBSERVATION *)realloc( rval,\n                               (n_allocated + 1) * sizeof( OBSERVATION));\n               }\n            rval[count] = obs;\n            if( !rval[count].observer_loc[0] && !rval[count].observer_loc[1]\n                                             && !rval[count].observer_loc[2])\n               {\n               const OBSERVATION temp_swap = rval[count];   /* put obs w/out positions */\n                                                            /* at the start of array   */\n               rval[count] = rval[n_obs_without_loc];\n               rval[n_obs_without_loc] = temp_swap;\n               n_obs_without_loc++;\n               }\n            count++;\n            if( count % 500000 == 0)\n               {\n               const clock_t dt = clock( ) - t0;\n\n               if( dt)\n                  fprintf( stderr, \"%ld obs (%.0lf obs/second) \\r\",\n                        count, (double)count * (double)CLOCKS_PER_SEC / (double)dt);\n               }\n            }\n         }\n      else if( !strncmp( buff, \"COM verbose \", 12))\n         verbose = atoi( buff + 12) + 1;\n      else if( !strcmp( buff, \"COM check tles\"))\n         check_all_tles = true;\n      else if( !memcmp( buff, \"COM Long.\", 9))\n         strlcpy_error( xxx_location, buff);\n      else if( !strcmp( buff, \"COM ignore obs\"))\n         while( fgets_trimmed( buff, sizeof( buff), ifile))\n            if( !strcmp( buff, \"COM end ignore obs\"))\n               break;\n   *n_found = count;\n   free_ades2mpc_context( ades_context);\n\n            /* for each spacecraft offset,  look for the corresponding\n            observation.  If we don't find one,  emit a warning. */\n   for( i = 0; i < n_offsets; i++)\n      {\n      size_t j = 0;\n\n      while( j < n_obs_without_loc && !offset_matches_obs( offsets + i, rval + j))\n         j++;\n      if( j == count)\n         {\n         if( n_errors_found++ < 10)\n            fprintf( stderr, REVERSE_VIDEO \"Unmatched artsat obs for JD %f\\n\"\n                     NORMAL_VIDEO, offsets[i].jd);\n         }\n      else\n         memcpy( rval[j].observer_loc, offsets[i].posn, 3 * sizeof( double));\n      }\n          /* Warn about obs with no offset data,  too. */\n   for( i = 0; i < count; i++)\n      if( !rval[i].observer_loc[0] && !rval[i].observer_loc[1] && !rval[i].observer_loc[2])\n         if( n_errors_found++ < 10)\n            fprintf( stderr, REVERSE_VIDEO \"No position for this observation :\\n%s\\n\"\n                           NORMAL_VIDEO, rval[i].text);\n   free( offsets);\n   if( n_errors_found >= 10)\n      fprintf( stderr, \"Showing first ten of %d errors\\n\", n_errors_found);\n   return( rval);\n}\n\nstatic int id_compare( const OBSERVATION *a, const OBSERVATION *b)\n{\n   return( memcmp( a->text, b->text, 12));\n}\n\nstatic int compare_obs( const void *a, const void *b, void *context)\n{\n   const OBSERVATION *aptr = (const OBSERVATION *)a;\n   const OBSERVATION *bptr = (const OBSERVATION *)b;\n   int rval = id_compare( aptr, bptr);\n\n   INTENTIONALLY_UNUSED_PARAMETER( context);\n   if( !rval)        /* same IDs?  Then sort by JD of observation */\n      rval = (aptr->jd > bptr->jd ? 1 : -1);\n   return( rval);\n}\n\n/* Copied straight from 'shellsor.cpp' in Find_Orb.  See comments there. */\n\nvoid shellsort_r( void *base, const size_t n_elements, const size_t elem_size,\n         int (*compare)(const void *, const void *, void *), void *context)\n{\n#if (defined _GNU_SOURCE || defined __GNU__ || defined __linux)\n   qsort_r( base, n_elements, elem_size, compare, context);\n#else\n   size_t gap = 250104703;\n   char *data = (char *)base;\n   char *pivot = (char *)alloca( elem_size);\n\n   while( gap < n_elements)\n      gap = gap * 8 / 3 + 1;\n   while( (gap = gap * 3 / 8) != 0)\n      {\n      size_t j;\n      const size_t spacing = elem_size * gap;\n\n      for( j = gap; j < n_elements; j++)\n         {\n         char *tptr = data + j * elem_size;\n         char *tptr2 = tptr - spacing;\n\n         if( (compare)( tptr2, tptr, context) > 0)\n            {\n            memcpy( pivot, tptr, elem_size);\n            memcpy( tptr, tptr2, elem_size);\n            tptr = tptr2;\n            tptr2 -= spacing;\n            while( tptr2 >= base && (compare)( tptr2, pivot, context) > 0)\n               {\n               memcpy( tptr, tptr2, elem_size);\n               tptr = tptr2;\n               tptr2 -= spacing;\n               }\n            memcpy( tptr, pivot, elem_size);\n            }\n         }\n      }\n#endif\n}\n\n/* Given a unit vector,  this creates a perpendicular xi_vect\nin the xy plane and an eta_vect perpendicular to them both. */\n\nstatic void create_orthogonal_vects( const double *v, double *xi_vect, double *eta_vect)\n{\n   const double tval = sqrt( v[0] * v[0] + v[1] * v[1]);\n\n   xi_vect[2] = 0.;\n   if( !tval)     /* 'mid' is directly at a celestial pole */\n      {\n      xi_vect[0] = 1.;\n      xi_vect[1] = 0.;\n      }\n   else\n      {\n      xi_vect[0] =  v[1] / tval;\n      xi_vect[1] = -v[0] / tval;\n      }\n   vector_cross_product( eta_vect, v, xi_vect);\n}\n\n/* relative_motion() is intended for situations where you've\n   computed that an object moved from p1 to p2,  and want to\n   compare that motion vector to an observed motion from p3 to\n   p4.  Initial use cases are in Sat_ID (we have a computed motion\n   from TLEs and two observed RA/decs) and astcheck (similar,  but\n   the computed positions are from orbital elements).  In each case,\n   you're trying to determine if the observed and computed motions\n   match to within some threshhold.\n\n   I used to do this by computing (delta_RA * cos_dec, delta_dec)\n   for both observed and computed motions.  That works well as long as\n   the two points are very close together.  As they're separated,  you\n   get distortions.  This should be more accurate.\n\n   Given four points\n         (ra_dec[0], ra_dec[1]) = starting point object 1\n         (ra_dec[2], ra_dec[3]) = ending point object 1\n         (ra_dec[4], ra_dec[5]) = starting point object 2\n         (ra_dec[6], ra_dec[7]) = ending point object 2\n   we compute their (xi, eta) sky plane coordinates,  using a plane\n   tangent to the midpoint of the starting locations.  That way,  any\n   distortion will affect both ends equally.  Then we compute how far\n   each object moved in the sky plane coordinates.  Then we compute\n   the differences in speed.              */\n\ndouble relative_motion( const double *ra_dec)\n{\n   double mid[3], xi_vect[3], eta_vect[3];\n   double xi[4], eta[4], v[4][3];\n   double delta_xi, delta_eta;\n   int i;\n\n   for( i = 0; i < 4; i++)\n      polar3_to_cartesian( v[i], ra_dec[i + i], ra_dec[i + i + 1]);\n\n   for( i = 0; i < 3; i++)\n      mid[i] = v[0][i] + v[1][i] + v[2][i] + v[3][i];\n   normalize_vect3( mid);\n   create_orthogonal_vects( mid, xi_vect, eta_vect);\n   for( i = 0; i < 4; i++)\n      {\n      const double dist = dot_product( mid, v[i]);\n\n      xi[i] = dot_product( xi_vect, v[i]) / dist;\n      eta[i] = dot_product( eta_vect, v[i]) / dist;\n      }\n   delta_xi =  (xi[0]  - xi[1])  - (xi[2]  - xi[3]);\n   delta_eta = (eta[0] - eta[1]) - (eta[2] - eta[3]);\n   return( sqrt( delta_xi * delta_xi + delta_eta * delta_eta));\n}\n\nstatic double angular_sep( const double delta_ra, const double dec1,\n            const double dec2, double *posn_ang)\n{\n   double p1[2], p2[2], dist;\n\n   p1[0] = 0.;\n   p1[1] = dec1;\n   p2[0] = delta_ra;\n   p2[1] = dec2;\n   calc_dist_and_posn_ang( p1, p2, &dist, posn_ang);\n   if( posn_ang)\n      *posn_ang *= 180. / PI;\n   return( dist);\n}\n\n/* Out of all observations for a given object,  this function will pick\ntwo that \"best\" describe the object's motion.  For that purpose,  we look\nfor a pair closest to 'optimal_dist' apart.  We also limit the separation\nin time to 'max_time_sep';  that's to avoid a situation where the observations\nare really close to the optimal distance apart,  but are actually from\ndifferent orbits.\n\nThis code thinks in terms of pairs of observations.  If somebody insists\non providing a single observation,  we duplicate it.  (Unless the '-1'\nswitch is specified,  in which case single observations are just dropped.)\n*/\n\nstatic bool include_singletons = true;\n\nstatic double find_good_pair( OBSERVATION *obs, const size_t n_obs,\n                   size_t *idx1, size_t *idx2)\n{\n   size_t a, b;\n   double speed = 0., dt;\n   const double max_time_sep = 0.1;  /* .1 day = 2.4 hr */\n   double best_score = 1e+30;\n\n   *idx1 = *idx2 = 0;\n   for( b = 0; b < n_obs; b++)\n      for( a = b + 1; a < n_obs\n                        && (dt = obs[a].jd - obs[b].jd) < max_time_sep; a++)\n         if( !memcmp( &obs[a].text[77], &obs[b].text[77], 3))\n            {\n            const double optimal_dist = PI / 180.;   /* one degree */\n            const double dist = angular_sep( obs[b].ra - obs[a].ra,\n                              obs[b].dec, obs[a].dec, NULL);\n            const double score = fabs( dist - optimal_dist);\n\n            assert( dt >= .0);\n            if( best_score > score)\n               {\n               best_score = score;\n               *idx2 = a;\n               *idx1 = b;\n               speed = dist / dt;\n               }\n            }\n   speed *= 180. / PI;    /* cvt speed from radians/day to deg/day */\n   speed /= hours_per_day;    /* ...then to deg/hour = arcmin/minute */\n   return( speed);\n}\n\n/* Aberration from the Ron-Vondrak method,  from Meeus'\n_Astronomical Algorithms_, p 153,  just the leading terms */\n\nstatic void compute_aberration( const double t_cen, double *ra, double *dec)\n{\n   const double l3 = 1.7534703 + 628.3075849 * t_cen;\n   const double sin_l3 = sin( l3), cos_l3 = cos( l3);\n   const double sin_2l3 = 2. * sin_l3 * cos_l3;\n   const double cos_2l3 = 2. * cos_l3 * cos_l3 - 1.;\n   const double x = -1719914. * sin_l3 - 25. * cos_l3\n                       +6434. * sin_2l3 + 28007 * cos_2l3;\n   const double y = 25. * sin_l3 + 1578089 * cos_l3\n                +25697. * sin_2l3 - 5904. * cos_2l3;\n   const double z = 10. * sin_l3 + 684185. * cos_l3\n                +11141. * sin_2l3 - 2559. * cos_2l3;\n   const double c = 17314463350.;    /* speed of light is 173.1446335 AU/day */\n   const double sin_ra = sin( *ra), cos_ra = cos( *ra);\n\n   *ra -= (y * cos_ra - x * sin_ra) / (c * cos( *dec));\n   *dec += ((x * cos_ra + y * sin_ra) * sin( *dec) - z * cos( *dec)) / c;\n}\n\nstatic void error_exit( const int exit_code)\n{\n   printf(\n\"sat_id takes the name of an input file of MPC-formatted (80-column)\\n\\\nastrometry as a command-line argument.  It searches for matches between\\n\\\nthe observation data and satellites in TLEs specified in 'tle_list.txt'.\\n\\\nBy default,  matches within .2 degrees are shown.\\n\\n\\\nAdditional command-line arguments are:\\n\\\n   -a YYYYMMDD  Only use observations after this time\\n\\\n   -b YYYYMMDD  Only use observations before this time\\n\\\n   -c           Check all TLEs for existence\\n\\\n   -m (nrevs)   Only consider objects with fewer # revs/day (default=6)\\n\\\n   -n (NORAD)   Only consider objects with this NORAD identifier\\n\\\n   -r (radius)  Only show matches within this radius in degrees (default=4)\\n\\\n   -t (fname)   Get TLEs from this filename\\n\\\n   -v           Verbose output. '-v2' gets still more verboseness.\\n\\\n   -y           Set tolerance for apparent motion mismatch\\n\\\n   -z (rate)    Only consider observations above 'rate' deg/hr (default=.001)\\n\\\n   \\n\\\n   See 'sat_id.txt' for additional details.\\n\");\n   exit( exit_code);\n}\n\nstatic int compute_artsat_ra_dec( double *ra, double *dec, double *dist,\n         const OBSERVATION *optr, tle_t *tle,\n         const double *sat_params, bool *in_shadow)\n{\n   double pos[3]; /* Satellite position vector */\n   double sun_xyzr[4], tval;\n   double t_since = (optr->jd - tle->epoch) * minutes_per_day;\n   const double j2000 = 2451545.;      /* JD 2451545 = 2000 Jan 1.5 */\n   int sxpx_rval;\n\n   if( select_ephemeris( tle))\n      sxpx_rval = SDP4( t_since, tle, sat_params, pos, NULL);\n   else\n      sxpx_rval = SGP4( t_since, tle, sat_params, pos, NULL);\n\n   if( sxpx_rval == SXPX_WARN_PERIGEE_WITHIN_EARTH)\n      sxpx_rval = 0;\n   if( verbose > 2 && sxpx_rval)\n      printf( \"TLE failed for JD %f: %d\\n\", optr->jd, sxpx_rval);\n   get_satellite_ra_dec_delta( optr->observer_loc, pos, ra, dec, dist);\n   compute_aberration( (optr->jd - j2000) / 36525., ra, dec);\n   lunar_solar_position( optr->jd, NULL, sun_xyzr);\n   ecliptic_to_equatorial( sun_xyzr);\n   tval = dot_product( sun_xyzr, pos);\n   if( tval < 0. && in_shadow)    /* elongation greater than 90 degrees; */\n      {                           /* may be in earth's shadow */\n      double tvect[3], r2 = 0.;\n      const double earth_r = EARTH_MAJOR_AXIS / 1000.;   /* in km */\n      size_t i;\n\n      tval /= sun_xyzr[3] * sun_xyzr[3];\n      for( i = 0; i < 3; i++)\n         {\n         tvect[i] = pos[i] - sun_xyzr[i] * tval;\n         r2 += tvect[i] * tvect[i];\n         }\n      *in_shadow = (r2 < earth_r * earth_r);\n      }\n   else if( in_shadow)\n      *in_shadow = false;\n   return( sxpx_rval);\n}\n\nstatic bool is_in_range( const double jd, const double tle_start,\n                                             const double tle_range)\n{\n   return( !tle_range || !tle_start ||\n            (jd >= tle_start && jd <= tle_start + tle_range));\n}\n\n/* Determines if we have _any_ observations between the given JDs.  If we\ndon't,  we can skip an individual TLE or an entire file.  */\n\nstatic bool got_obs_in_range( const object_t *objs, size_t n_objects,\n               const double jd_start, const double jd_end)\n{\n   while( n_objects--)\n      {\n      OBSERVATION *obs = objs->obs;\n      size_t i;\n\n      if( obs[0].jd < jd_end && obs[objs->n_obs - 1].jd > jd_start)\n         for( i = 0; i < objs->n_obs; i++)\n            if( obs[i].jd > jd_start && obs[i].jd < jd_end)\n               return( true);\n      objs++;\n      }\n   return( false);\n}\n\nstatic int _pack_intl_desig( char *desig_out, const char *desig)\n{\n   size_t i = 0;\n   int rval = 0;\n\n   while( desig[i] && isdigit( desig[i]))\n      i++;\n   memset( desig_out, ' ', 8);\n   desig_out[8] = '\\0';\n   if( i == 5 && isupper( desig[5]))      /* already in YYYNNAaa form */\n      {\n      memcpy( desig_out, desig, 5);\n      desig += 5;\n      }\n   else if( i == 4 && desig[4] == '-' && isdigit( desig[5])\n                  && isdigit( desig[6]) && isdigit( desig[7]) && isupper( desig[8]))\n      {\n      desig_out[0] = desig[2];      /* desig is in YYYY-NNNAaa form */\n      desig_out[1] = desig[3];\n      desig_out[2] = desig[5];\n      desig_out[3] = desig[6];\n      desig_out[4] = desig[7];\n      desig += 8;\n      }\n   else                 /* not a recognized intl desig form */\n      rval = -1;\n   if( !rval)\n      {\n      desig_out[5] = *desig++;\n      if( isupper( *desig))\n         desig_out[6] = *desig++;\n      if( isupper( *desig))\n         desig_out[7] = *desig++;\n      }\n   return( rval);\n}\n\nstatic int _compare_intl_desigs( const char *desig1, const char *desig2)\n{\n   char odesig1[9], odesig2[9];\n\n   _pack_intl_desig( odesig1, desig1);\n   _pack_intl_desig( odesig2, desig2);\n   return( strcmp( odesig1, odesig2));\n}\n\n/* Code to look through 'sat_xref.txt',  if available,  and assign NORAD\nand international designations to TLEs with only default designations.\nSee 'sat_xref.txt' and 'eph2tle.cpp' in the Find_Orb repository. */\n\nstatic int look_up_extended_identifiers( const char *line0, tle_t *tle)\n{\n   static char buff[100];\n   int match_found = !strcmp( line0, buff + 21);\n   static bool got_sat_xref_txt = true;  /* until proven otherwise */\n\n   if( !match_found && got_sat_xref_txt)\n      {\n      FILE *ifile = local_then_config_fopen( \"sat_xref.txt\", \"rb\");\n\n      if( !ifile)    /* don't look for it again */\n         got_sat_xref_txt = false;\n      else\n         {\n         while( !match_found && fgets_trimmed( buff, sizeof( buff), ifile))\n            match_found = !strcmp( line0, buff + 21);\n         fclose( ifile);\n         }\n      }\n   if( match_found)\n      {\n      tle->norad_number = atoi( buff);\n      memcpy( tle->intl_desig, buff + 12, 8);\n      }\n   return( match_found);\n}\n\n/* The international (YYYY-NNNletter(s)) designation and the NORAD\nfive-digit designation are always shown for a match.  If they're in\nthe name as well,  we should remove them so that more of the actual\nname gets displayed.  */\n\nstatic void remove_redundant_desig( char *name, const char *desig)\n{\n   size_t len = strlen( desig), i;\n\n   while( len && desig[len - 1] == ' ')\n      len--;\n   for( i = 0; name[i]; i++)\n      if( !memcmp( name + i, desig, len))\n         {\n         size_t n = len;\n\n         if( i >= 3 && name[i - 2] == '=' && name[i - 1] == ' '\n                                          && name[i - 3] == ' ')\n            {\n            i -= 3;     /* remove preceding '=' */\n            n += 3;\n            }\n         else if( name[i + n] == ' ' && name[i + n + 1] == '='\n                                     && name[i + n + 2] == ' ')\n            n += 3;\n         memmove( name + i, name + i + n, strlen( name + i + n) + 1);\n         i--;\n         }\n}\n\nstatic char *gzgets_trimmed( gzFile ifile, char *buff, const size_t buff_len)\n{\n   char *rval = gzgets( ifile, buff, (int)buff_len);\n\n   if( rval)\n      {\n      size_t len = strlen( buff);\n\n      while( len && buff[len - 1] <= ' ')\n         len--;\n      buff[len] = '\\0';\n      }\n   return( rval);\n}\n\n/* We check astrometry first against TLEs from github.com/Bill-Gray/tles,\nthen some other sources such as the amateur community's TLEs,  and\nonly then against Space-Track TLEs.  If we've already checked an\nobject against the previous sources,  using TLEs whose date range would\ncover that object,  then we really ought not to check the Space-Track\nTLEs.  For one thing,  the mere fact that we've gone to the effort of\ncomputing \"our own\" TLEs means the Space-Track TLEs are unreliable (some\nhave poor accuracy;  others are not consistently available).\n\nTherefore,  we maintain a list of '# ID:' numbers from tle_list.txt,  and when we\nget to '# ID off',  we figure we're in Space-Track TLE territory.  If we\nencounter a NORAD number that's in our list,  we essentially say :\nwe already found this object and have handled it. */\n\ntypedef struct\n{\n   int norad_number;\n   double min_jd, max_jd;\n} already_found_t;\n\nstatic bool already_found_desig( const int curr_norad, size_t n_found,\n            const already_found_t *found, double jd)\n{\n   bool rval = false;\n\n   for( size_t i = 0; !rval && i < n_found; i++)\n      rval = (curr_norad == found[i].norad_number && jd > found[i].min_jd && jd < found[i].max_jd);\n   return( rval);\n}\n\n   /* By default,  we warn you if TLEs are going to run out next week. */\n\nstatic double lookahead_warning_days = 7.;\n\n          /* The computed and observed motions should match,  but\n          (obviously) only to some tolerance.  A tolerance of 60\n          arcseconds seems to work. (May seem large,  but these\n          objects often 'streak' and have large residuals.)         */\n\ndouble motion_mismatch_limit = 60.;\n\n/* Given a set of MPC observations and a TLE file,  this function looks at\neach TLE in the file and checks to see if that satellite came close to any\nof the observations.  The function is called for each TLE file.\n*/\n\nint norad_id = 0;\nconst char *intl_desig = NULL;\n\nstatic int search_norad = 0;\nstatic char search_intl[12];\n\ndouble tle_start = 0., tle_range = 1e+9;\n\n/* The following may be reset for a particular object where we have TLEs\nthat we expect have a certain maximum expected error,  using a line\nstarting with # Max error.  This lets us use a large search radius for\nsome objects with poor TLEs,  but restrict the radius tightly for objects\nthat we know we have a good handle on.  */\n\nstatic double max_expected_error = 180.;\nstatic int n_tles_expected_in_file = 0;\n\nstatic int add_tle_to_obs( object_t *objects, const size_t n_objects,\n             const char *tle_file_name, const double search_radius,\n             const double max_revs_per_day)\n{\n   char line0[100], line1[100], line2[100];\n   gzFile tle_file;\n   int rval = 0, n_tles_found = 0;\n   bool check_updates = true;\n   bool look_for_tles = true;\n   static bool error_check_date_ranges = true;\n   const clock_t time_started = clock( );\n   static already_found_t *norad_ids = NULL;\n   static size_t n_norad_ids = 0;\n   const double mjd_1970 = 40587.;     /* MJD for 1970 Jan 1 */\n   const double curr_mjd = mjd_1970 + (double)time( NULL) / 86400.;\n\n   if( !tle_file_name)     /* flag to free internal memory at shutdown */\n      {\n      if( norad_ids)\n         free( norad_ids);\n      norad_ids = NULL;\n      n_norad_ids = 0;\n      return( 0);\n      }\n   tle_file = gzopen( tle_file_name, \"rb\");\n   if( !tle_file)\n      {\n      char buff[200];      /* try again with .gz added to the filename */\n\n      strlcpy_error( buff, tle_file_name);\n      strlcat_error( buff, \".gz\");\n      tle_file = gzopen( buff, \"rb\");\n      }\n   if( !tle_file)\n      {\n#ifdef ON_LINE_VERSION\n      printf( \"<h1> WARNING : '%s' not opened<br>\\n\", tle_file_name);\n      printf( \"Please e-mail pluto\\x40projectpluto\\x2e\\x63om about this. </h1>\\n\");\n#else\n      fprintf( stderr, \"WARNING : '%s' not opened\\n\", tle_file_name);\n      fprintf( stderr, \"Please e-mail pluto\\x40projectpluto\\x2e\\x63om about this.\\n\");\n#endif\n      return( -1);\n      }\n   if( verbose)\n      printf( \"Looking through TLE file '%s', %u objs, radius %f, max %f revs/day\\n\",\n                 tle_file_name, (unsigned)n_objects, search_radius, max_revs_per_day);\n   *line0 = *line1 = '\\0';\n   while( gzgets_trimmed( tle_file, line2, sizeof( line2)))\n      {\n      tle_t tle;  /* Structure for two-line elements set for satellite */\n      const double mins_per_day = 24. * 60.;\n      bool is_a_tle = false;\n\n      if( verbose > 3)\n         printf( \"%s\\n\", line2);\n      if( look_for_tles && parse_elements( line1, line2, &tle) >= 0)\n         {\n         is_a_tle = true;\n         n_tles_found++;\n         if( tle.norad_number == 99999)\n            look_up_extended_identifiers( line0, &tle);\n         if( line0[0] == '0' && line0[1] == ' ')\n            memmove( line0, line0 + 2, strlen( line0 + 1));\n         if( (search_norad && search_norad != tle.norad_number)\n               || (*search_intl && strcmp( search_intl, tle.intl_desig)))\n            {\n            fprintf( stderr, REVERSE_VIDEO \"WARNING: desigs mismatch the 'ID' field : %s\"\n                        NORMAL_VIDEO \"\\n\", tle_file_name);\n            fprintf( stderr, \"NORAD IDs are %d, %d\\n\", search_norad, tle.norad_number);\n            fprintf( stderr, \"Int'l IDs are '%s', '%s'\\n\", search_intl, tle.intl_desig);\n            search_norad = 0;       /* suppress cascading errors */\n            *search_intl = '\\0';\n            }\n         }\n      if( is_a_tle && (tle.ephemeris_type == 'H'\n                 || tle.xno < 2. * PI * max_revs_per_day / mins_per_day)\n                 && (!norad_id || norad_id == tle.norad_number)\n                 && (!intl_desig || !_compare_intl_desigs( tle.intl_desig, intl_desig)))\n         {                           /* hey! we got a TLE! */\n         double sat_params[N_SAT_PARAMS];\n         size_t idx;\n\n         if( verbose > 1)\n            printf( \"TLE found:\\n%s\\n%s\\n\", line1, line2);\n         if( select_ephemeris( &tle))\n            SDP4_init( sat_params, &tle);\n         else\n            SGP4_init( sat_params, &tle);\n         for( idx = 0; idx < n_objects; idx++)\n            {\n            object_t *obj_ptr = objects + idx;\n            const OBSERVATION *optr1 = obj_ptr->obs + obj_ptr->idx1;\n            const OBSERVATION *optr2 = obj_ptr->obs + obj_ptr->idx2;\n\n            assert( obj_ptr->idx1 <= obj_ptr->idx2);\n            assert( optr2->jd >= optr1->jd);\n            if( is_in_range( optr1->jd, tle_start, tle_range) &&\n                 (search_norad || !already_found_desig( tle.norad_number, n_norad_ids, norad_ids, optr1->jd)))\n               {\n               double radius;\n               double ra, dec, dist_to_satellite;\n               int sxpx_rval;\n               size_t i = 0;\n               bool in_shadow;\n\n               sxpx_rval = compute_artsat_ra_dec( &ra, &dec, &dist_to_satellite,\n                              optr1, &tle, sat_params, &in_shadow);\n               radius = angular_sep( ra - optr1->ra, dec, optr1->dec, NULL) * 180. / PI;\n               while( i < obj_ptr->n_matches\n                       && obj_ptr->matches[i].norad_number != tle.norad_number\n                       && obj_ptr->matches[i].norad_number)\n                  i++;\n               if( !sxpx_rval && radius < search_radius      /* good enough for us! */\n                       && radius < max_expected_error\n                       && i == obj_ptr->n_matches)\n                  {\n                  double dt = optr2->jd - optr1->jd;\n                  const double min_dt = 1e-6;   /* 0.0864 seconds */\n                  double motion_diff, ra2, dec2;\n                  double temp_array[8];\n                  bool show_computed_motion = true;\n\n                  assert( dt >= 0.);\n                  if( !dt)\n                     {\n                     OBSERVATION temp_obs = *optr2;\n                     double dist2;\n\n                     temp_obs.jd += min_dt;\n                     if( memcmp( temp_obs.text + 77, \"247\", 3))\n                        set_observer_location( &temp_obs);\n                     if( vector3_length( optr2->observer_loc) > 6400.)\n                        show_computed_motion = false;   /* spacecraft-based obs */\n                     compute_artsat_ra_dec( &ra2, &dec2, &dist2,\n                              &temp_obs, &tle, sat_params, NULL);\n                     }\n                  else\n                     compute_artsat_ra_dec( &ra2, &dec2, &dist_to_satellite,\n                              optr2, &tle, sat_params, NULL);\n                  temp_array[0] = ra;     /* starting point (computed) */\n                  temp_array[1] = dec;\n                  temp_array[2] = ra2;    /* ending point (computed) */\n                  temp_array[3] = dec2;\n                  temp_array[4] = optr1->ra;  /* starting point (observed) */\n                  temp_array[5] = optr1->dec;\n                  temp_array[6] = optr2->ra;  /* ending point (observed) */\n                  temp_array[7] = optr2->dec;\n                  if( !dt)\n                     motion_diff = 0.;\n                  else\n                     motion_diff = relative_motion( temp_array);\n                  motion_diff *= 3600. * 180. / PI;  /* cvt to arcseconds */\n                  if( motion_diff < motion_mismatch_limit)\n                     {\n                     char obuff[200];\n                     char full_intl_desig[20];\n                     double motion_rate = 0., motion_pa = 0.;\n                     const double arcminutes_per_radian = 60. * 180. / PI;\n\n                     motion_rate = angular_sep( optr1->ra - optr2->ra,\n                                          optr1->dec, optr2->dec, &motion_pa);\n                     motion_rate *= arcminutes_per_radian;\n                     if( dt)\n                        motion_rate /= dt * minutes_per_day;\n                     line1[8] = line1[16] = '\\0';\n                     memcpy( line1 + 30, line1 + 11, 6);\n                     line1[11] = '\\0';\n                     i = 0;\n                     while( i < obj_ptr->n_matches && radius > obj_ptr->matches[i].dist)\n                        i++;\n                     obj_ptr->matches = (match_t *)realloc( obj_ptr->matches,\n                                    (obj_ptr->n_matches + 1) * sizeof( match_t));\n                     memmove( obj_ptr->matches + i + 1, obj_ptr->matches + i,\n                              (obj_ptr->n_matches - i) * sizeof( match_t));\n                     memset( obj_ptr->matches + i, 0, sizeof( match_t));\n                     obj_ptr->matches[i].dist = radius;\n                     obj_ptr->matches[i].norad_number = tle.norad_number;\n                     obj_ptr->n_matches++;\n                     strncpy( obj_ptr->matches[i].intl_desig,\n                                                      tle.intl_desig, 9);\n                     snprintf_err( full_intl_desig, sizeof( full_intl_desig), \"%s%.2s-%s\",\n                              (tle.intl_desig[0] < '5' ? \"20\" : \"19\"),\n                              tle.intl_desig, tle.intl_desig + 2);\n                     snprintf_err( obuff, sizeof( obuff), \"     %05dU = %-11s \",\n                           tle.norad_number, full_intl_desig);\n                     if( tle.ephemeris_type != 'H')\n                        snprintf_append( obuff, sizeof( obuff),\n                               \"e=%.2f; P=%.1f min; i=%.1f\",\n                               tle.eo, 2. * PI / tle.xno,\n                               tle.xincl * 180. / PI);\n                     if( tle_checksum( line0))         /* object name given... */\n                        {\n                        char norad_desig[20];\n\n                        remove_redundant_desig( line0, full_intl_desig);\n                        snprintf( norad_desig, sizeof( norad_desig),\n                                           \"NORAD %05d\", tle.norad_number);\n                        remove_redundant_desig( line0, norad_desig);\n                        snprintf_append( obuff, sizeof( obuff), \": %s\", line0);\n                        }\n                     strlcpy( obj_ptr->matches[i].text, obuff + 26, sizeof( obj_ptr->matches[i].text));\n                     obuff[79] = '\\0';    /* avoid buffer overrun */\n//                   snprintf_append( obuff, sizeof( obuff), \" motion %f\", motion_diff);\n                     strlcat_error( obuff, \"\\n\");\n                     if( !dt)\n                        strlcat_error( obuff,\n                              \"             no observed motion (single obs) \");\n                     else\n                        snprintf_append( obuff, sizeof( obuff),\n                              \"             motion %7.4f\\\"/sec at PA %5.1f;\",\n                              motion_rate, motion_pa);\n                     snprintf_append( obuff, sizeof( obuff),\n                                   \" dist=%8.1f km; offset=%7.4f deg\\n\",\n                                   dist_to_satellite, radius);\n                              /* \"Speed\" is displayed in arcminutes/second,\n                                  or in degrees/minute */\n                     if( verbose || !field_mode)\n                        {\n                        printf( \"%s\\n\", optr1->text);\n                        printf( \"%s\", obuff);\n#ifdef SHOW_RA_DEC_OFFSETS\n                        printf( \"dRA = %.3f  dDec = %.3f\\n\",\n                                    (ra - optr1->ra) * 180. / PI,\n                                    (dec - optr1->dec) * 180. / PI);\n#endif\n                        }\n                     motion_rate = angular_sep( ra - ra2, dec, dec2, &motion_pa);\n                     motion_rate *= arcminutes_per_radian;\n                     if( dt)\n                        motion_rate /= dt * minutes_per_day;\n                     else\n                        motion_rate /= min_dt * minutes_per_day;\n                     if( show_computed_motion && (verbose || !field_mode))\n                        printf( \"             motion %7.4f\\\"/sec at PA %5.1f (computed)\\n\\n\",\n                            motion_rate, motion_pa);\n                     obj_ptr->matches[i].ra = ra;\n                     obj_ptr->matches[i].dec = dec;\n                     obj_ptr->matches[i].motion_rate = motion_rate;\n                     obj_ptr->matches[i].motion_pa = motion_pa;\n                     obj_ptr->matches[i].in_shadow = in_shadow;\n                     }\n                  }\n               }\n            }\n         }\n      else if( !strncmp( line2, \"# No updates\", 12))\n         check_updates = false;\n      else if( !strncmp( line2, \"# Max error\",  11))\n         max_expected_error = atof( line2 + 12);\n      else if( !strncmp( line2, \"# TLEs expected:\", 16))\n         n_tles_expected_in_file = atoi( line2 + 17);\n      else if( !strncmp( line2, \"# Range_check \", 14))\n         error_check_date_ranges = (line2[14] != '0');\n      else if( !strncmp( line2, \"# Ephem range:\", 14))\n         {\n         double mjd_start, mjd_end, tle_step;\n         int n_read;\n\n         n_read = sscanf( line2 + 14, \"%lf %lf %lf\\n\", &mjd_start, &mjd_end, &tle_step);\n         assert( n_read == 3);\n         if( error_check_date_ranges && tle_start)   /* do date ranges specified */\n            {              /* in tle_list.txt and in the TLE file itself match? */\n            const double tolerance = 0.001;     /* an allowance for roundoff */\n\n            if( fabs( mjd_start + 2400000.5 - tle_start) > tolerance)\n               {\n               char time_buff[40];\n\n               fprintf( stderr, REVERSE_VIDEO \"WARNING: starting date for TLEs in '%s' \"\n                        \"mismatches that in tle_list.txt\\n\" NORMAL_VIDEO, tle_file_name);\n               full_ctime( time_buff, tle_start, FULL_CTIME_YMD);\n               fprintf( stderr, \"TLE list start MJD %f = %s\\n\",\n                                             tle_start - 2400000.5, time_buff);\n               full_ctime( time_buff, mjd_start + 2400000.5, FULL_CTIME_YMD);\n               fprintf( stderr, \"'Range:' line start MJD %f = %s\\n\", mjd_start, time_buff);\n               fprintf( stderr, \"diff = %f\\n\",\n                        mjd_start + 2400000.5 - tle_start);\n               }\n            if( fabs( mjd_end + 2400000.5 - tle_start - tle_range) > tolerance)\n               {\n               char time_buff[40];\n\n               fprintf( stderr, REVERSE_VIDEO \"WARNING: ending date for TLES in '%s' \"\n                        \"mismatches that in tle_list.txt\\n\" NORMAL_VIDEO, tle_file_name);\n               full_ctime( time_buff, tle_start + tle_range, FULL_CTIME_YMD);\n               fprintf( stderr, \"TLE list ends MJD %f = %s\\n\",\n                             tle_start + tle_range - 2400000.5, time_buff);\n               full_ctime( time_buff, mjd_end + 2400000.5, FULL_CTIME_YMD);\n               fprintf( stderr, \"'Range:' line ends MJD %f = %s\\n\", mjd_end, time_buff);\n               fprintf( stderr, \"diff = %f\\n\",\n                        mjd_end + 2400000.5 - tle_start - tle_range);\n               }\n            }\n         tle_range = tle_step;\n         if( check_updates && mjd_end < curr_mjd + lookahead_warning_days)\n            {\n            char time_buff[40];\n\n            full_ctime( time_buff, mjd_end + 2400000.5, FULL_CTIME_YMD);\n            fprintf( stderr, REVERSE_VIDEO \"WARNING: TLEs in '%s' run out on %s (%.2f days)\\n\"\n                           NORMAL_VIDEO, tle_file_name, time_buff, mjd_end - curr_mjd);\n            }\n         if( !got_obs_in_range( objects, n_objects, mjd_start + 2400000.5,\n                                            mjd_end + 2400000.5) && !check_all_tles)\n            {\n            if( verbose)\n               fprintf( stderr, REVERSE_VIDEO \"'%s' contains no TLEs for our time range\\n\"\n                               NORMAL_VIDEO, tle_file_name);\n            gzclose( tle_file);\n            return( 0);\n            }\n         }\n      else if( !memcmp( line2, \"# ID: \", 6))\n         {\n         int n_read = sscanf( line2 + 6, \"%d %s\", &search_norad, search_intl);\n         size_t i;\n         bool is_natural;\n\n         assert( 2 == n_read);\n         for( i = strlen( search_intl); i < 8; i++)\n            search_intl[i] = ' ';\n         search_intl[8] = '\\0';\n         is_natural = isdigit( search_intl[7]);\n         if( identify_filter == IDENTIFY_ARTSATS && is_natural)\n            look_for_tles = false;\n         if( identify_filter == IDENTIFY_NATSATS && !is_natural)\n            look_for_tles = false;\n         }\n      else if( !memcmp( line2, \"# ID off\", 8))\n         {\n         search_norad = 0;\n         *search_intl = '\\0';\n         if( my_tles_only)\n            break;\n         }\n      else if( !memcmp( line2, \"# Range: \", 9))\n         {\n         int n_read;\n         char start[20], end[20];\n\n         n_read = sscanf( line2 + 9, \"%19s %19s\", start, end);\n         assert( n_read == 2);\n         tle_start = get_time_from_string( 0, start, FULL_CTIME_YMD, NULL);\n         tle_range = get_time_from_string( 0, end, FULL_CTIME_YMD, NULL) - tle_start;\n         if( !check_all_tles)\n            look_for_tles = got_obs_in_range( objects, n_objects, tle_start,\n                                 tle_start + tle_range);\n         }\n      else if( !memcmp( line2, \"# MJD \", 6))\n         {\n         tle_start = atof( line2 + 6) + 2400000.5;\n//       look_for_tles = got_obs_in_range( objects, n_objects, tle_start,\n//                               tle_start + tle_range);\n         }\n      else if( !memcmp( line2, \"# Include \", 10))\n         {\n         if( look_for_tles)\n            {\n            char iname[255];\n            size_t i = strlen( tle_file_name);\n            const double saved_max_expected_error = max_expected_error;\n\n            while( i && tle_file_name[i - 1] != '/' && tle_file_name[i - 1] != '\\\\')\n               i--;\n            memcpy( iname, tle_file_name, i);\n            strlcpy_err( iname + i, line2 + 10, sizeof( iname) - i);\n            if( verbose > 1)\n               printf( \"Including '%s'\\n\", iname);\n            if( search_norad)\n               {                           /* add newly-found ID */\n               if( !n_norad_ids)\n                  norad_ids = (already_found_t *)malloc( sizeof( already_found_t));\n               else if( is_power_of_two( n_norad_ids))\n                  norad_ids = (already_found_t *)realloc( norad_ids, 2 * n_norad_ids * sizeof( already_found_t));\n               norad_ids[n_norad_ids].norad_number = search_norad;\n               norad_ids[n_norad_ids].min_jd = tle_start;\n               norad_ids[n_norad_ids].max_jd = tle_start + tle_range;\n               n_norad_ids++;\n               }\n            rval = add_tle_to_obs( objects, n_objects, iname, search_radius,\n                                    max_revs_per_day);\n            max_expected_error = saved_max_expected_error;\n            }\n         tle_start = 0.;\n         tle_range = 1e+9;\n         look_for_tles = true;\n         }\n      else if( !memcmp( line2, \"# Warn \", 7))\n         {\n         char *tptr = strchr( line2, '|');\n         double jd_warn;\n\n         assert( tptr);\n         *tptr = '\\0';\n         jd_warn = get_time_from_string( 0, line2 + 7, FULL_CTIME_YMD, NULL);\n         if( jd_warn < curr_mjd + 2400000.5 + lookahead_warning_days)\n            fprintf( stderr, REVERSE_VIDEO \"WARNING: %s\" NORMAL_VIDEO \"\\n\",\n                           tptr + 1);\n         }\n      strlcpy_error( line0, line1);\n      strlcpy_error( line1, line2);\n      }\n   if( verbose)\n      printf( \"%d TLEs read from '%s', %.3f seconds\\n\", n_tles_found,\n                tle_file_name,\n                (double)( clock( ) - time_started) / (double)CLOCKS_PER_SEC);\n   if( n_tles_found < n_tles_expected_in_file)\n      {\n      const char *err_message = REVERSE_VIDEO\n               \"**** WARNING : %d TLEs were read from '%s'.  At least %d were expected.\\n\"\n                                NORMAL_VIDEO;\n\n#ifdef ON_LINE_VERSION\n      printf( err_message, n_tles_found, tle_file_name, n_tles_expected_in_file);\n#else\n      fprintf( stderr, err_message, n_tles_found, tle_file_name, n_tles_expected_in_file);\n#endif\n      printf( \"Please e-mail the author (pluto at projectpluto dot com) about this.\\n\");\n      }\n   gzclose( tle_file);\n   return( rval);\n}\n\n/* Output punch-card formatted astrometry is time-stamped when possible.\nThe scheme is the same as used for time-stamping NEOCP observations in\n'neocp.cpp' and 'neocp2.cpp' in the 'miscell' repository (q.v) and makes\nuse of the fact that columns 57 to 65 default to being spaces. */\n\nstatic void time_tag( char *tag)\n{\n   if( !memcmp( tag, \"     \", 5))\n      {\n      const time_t t0 = time( NULL);\n      struct tm tm;\n\n#if defined( _WIN32) || defined( __WATCOMC__)\n      memcpy( &tm, gmtime( &t0), sizeof( tm));\n#else\n      gmtime_r( &t0, &tm);\n#endif\n      tag[0] = '~';\n      tag[1] = int_to_mutant_hex_char( tm.tm_mon + 1);\n      tag[2] = int_to_mutant_hex_char( tm.tm_mday);\n      tag[3] = int_to_mutant_hex_char( tm.tm_hour);\n      tag[4] = int_to_mutant_hex_char( tm.tm_min);\n      }\n}\n\n/* Given a 'packed' international designation such as 92044A or\n10050BZ,  this outputs the 'unpacked/ desig 1992-044A or 2010-050BZ. */\n\nstatic char *unpack_intl( const char *packed, char *unpacked)\n{\n   snprintf( unpacked, 12, \"%s%.2s-%s\",\n            (atoi( packed) > 57000 ? \"19\" : \"20\"), packed, packed + 2);\n   return( unpacked);\n}\n\n/* The \"on-line version\",  sat_id2,  gathers data from a CGI multipart form,\n   puts it into a file,  possibly adds in some options,  puts together the\n   command-line arguments,  and then calls sat_id_main.  See 'sat_id2.cpp'.\n\n   By default,  TLE information is drawn from 'tle_list.txt';  this can\n   be overridden with the -t (filename) option.  The specified file is first\n   searched for in the current directory,  then in ~/.find_orb,  then in\n   ~/.find_orb/tles,  then in ~/tles.    */\n\n#ifdef ON_LINE_VERSION\nint sat_id_main( const int argc, const char **argv)\n#else\nint main( const int argc, const char **argv)\n#endif\n{\n   char tle_file_name[256];\n   const char *tname = \"tle_list.txt\";\n   const char *output_astrometry_filename = NULL;\n   bool output_only_matches = false;\n   const char *ifilename = NULL;\n   FILE *ifile;\n   OBSERVATION *obs;\n   object_t *objects;\n   size_t n_obs, n_objects;\n   double search_radius = 4;     /* default to 4-degree search */\n            /* Asteroid searchers _sometimes_ report Molniyas to me,\n            which make two revolutions a day.  This limit could safely\n            be set to three,  but few artsats are between 6 and 3 revs/day\n            (i.e.,  in four to eight-hour orbits).  So this doesn't result\n            in much extra checking. */\n   double max_revs_per_day = 6.;\n            /* Anything slower than 0.001'/sec is almost certainly not an\n            artsat.  We don't even bother to check those (unless the -z\n            option is used to reset this limit).  */\n   double speed_cutoff = 0.001;\n   double t_low = oct_4_1957;\n   double t_high = jan_1_2057;\n   int rval, i, j, prev_i;\n   bool show_summary = false, add_new_line = false, all_single = false;\n\n   if( argc == 1)\n      {\n      fprintf( stderr, \"No input file of astrometry specified on command line\\n\\n\");\n      error_exit( -2);\n      }\n\n   for( i = 1; i < argc; i++)\n      if( argv[i][0] == '-')\n         {\n         const char *param = argv[i] + 2;\n\n         if( !*param && i < argc - 1)\n            param = argv[i + 1];\n         switch( argv[i][1])\n            {\n            case '1':\n               include_singletons = false;\n               break;\n            case 'a':\n               t_low = get_time_from_string( 0, param, FULL_CTIME_YMD, NULL);\n               break;\n            case 'b':\n               t_high = get_time_from_string( 0, param, FULL_CTIME_YMD, NULL);\n               break;\n            case 'c':\n               check_all_tles = true;\n               break;\n            case 'd':\n               _target_desig = param;\n               break;\n            case 'f':\n               if( *param == 'n')\n                  identify_filter = IDENTIFY_NATSATS;\n               else if( *param == 'a')\n                  identify_filter = IDENTIFY_ARTSATS;\n               else\n                  {\n                  fprintf( stderr, \"Bad command line switch '%s'\\n\", argv[i]);\n                  fprintf( stderr, \"Use -fa to show only artsats,  -fn to show only natsats\\n\");\n                  return( -1);\n                  }\n               break;\n            case 'i':\n               intl_desig = param;\n               break;\n            case 'l':\n               lookahead_warning_days = atof( param);\n               break;\n            case 'r':\n               search_radius = atof( param);\n               break;\n            case 'y':\n               motion_mismatch_limit = atof( param);\n               break;\n            case 'm':\n               max_revs_per_day = atof( param);\n               break;\n            case 'n':\n               norad_id = atoi( param);\n               break;\n            case 'N':\n               add_new_line = true;\n               break;\n            case 'o':\n            case 'O':\n               output_astrometry_filename = param;\n               output_only_matches = (argv[i][1] == 'o');\n               break;\n            case 's':\n               my_tles_only = true;\n               break;\n            case 'S':      /* treat each observation individually */\n               all_single = true;\n               break;\n            case 't':\n               tname = param;\n               break;\n            case 'u':\n               show_summary = true;\n               break;\n            case 'v':\n               verbose = atoi( param) + 1;\n               break;\n            case 'z':\n               speed_cutoff = atof( param);\n               break;\n            default:\n               fprintf( stderr, \"Unrecognized command-line option '%s'\\n\", argv[i]);\n               error_exit( -3);\n               break;\n            }\n         }\n      else if( !ifilename)\n         ifilename = argv[i];\n   if( verbose)\n      for( i = 0; i < argc; i++)\n         printf( \"Arg %d: '%s'\\n\", i, argv[i]);\n\n   strlcpy_error( tle_file_name, tname);\n#if !defined( _WIN32) && !defined( __WATCOMC__)\n   if( access( tle_file_name, F_OK))\n      {\n      if( verbose)\n         fprintf( stderr, \"'%s' failed\\n\", tle_file_name);\n      make_config_dir_name( tle_file_name, tname);\n      if( access( tle_file_name, F_OK))\n         {\n         char buff[256];\n\n         if( verbose)\n            fprintf( stderr, \"'%s' failed\\n\", tle_file_name);\n         strlcpy_error( buff, \"tles/\");\n         strlcat_error( buff, tname);\n         make_config_dir_name( tle_file_name, buff);\n         if( access( tle_file_name, F_OK))\n            {\n            if( verbose)\n               fprintf( stderr, \"'%s' failed\\n\", tle_file_name);\n            strlcpy_error( tle_file_name, getenv( \"HOME\"));\n            strlcat_error( tle_file_name, \"/tles/\");\n            strlcat_error( tle_file_name, tname);\n            }\n         }\n      }\n#endif\n\n   assert( ifilename);\n   ifile = fopen( ifilename, \"rb\");\n   if( !ifile)\n      {\n      fprintf( stderr, \"Couldn't open input file %s\\n\", ifilename);\n      perror( NULL);\n      return( -1);\n      }\n   obs = get_observations_from_file( ifile, &n_obs, t_low, t_high);\n   printf( \"%d observations found\\n\", (int)n_obs);\n   if( !obs || !n_obs)\n      return( -2);\n   shellsort_r( obs, n_obs, sizeof( obs[0]), compare_obs, NULL);\n\n   for( n_objects = i = 0; (size_t)i < n_obs; i++)\n      if( !i || id_compare( obs + i - 1, obs + i) || field_mode || all_single)\n         n_objects++;\n   objects = (object_t *)calloc( n_objects, sizeof( object_t));\n   assert( objects);\n   if( !field_mode)\n      printf( \"%d objects\\n\", (int)n_objects);\n   for( n_objects = i = prev_i = 0; (size_t)i < n_obs; i++)\n      if( !i || id_compare( obs + i - 1, obs + i) || field_mode || all_single)\n         {\n         objects[n_objects].obs = obs + i;\n         if( n_objects)\n            objects[n_objects - 1].n_obs = i - prev_i;\n         n_objects++;\n         prev_i = i;\n         }\n   objects[n_objects - 1].n_obs = i - prev_i;\n   for( i = j = 0; (size_t)i < n_objects; i++)\n      {\n      objects[i].speed = find_good_pair( objects[i].obs,\n                  objects[i].n_obs, &objects[i].idx1, &objects[i].idx2);\n      if( (objects[i].speed >= speed_cutoff && objects[i].n_obs > 1)\n            || (include_singletons && objects[i].n_obs == 1))\n         {               /* fast enough to be considered */\n         objects[j] = objects[i];\n         j++;\n         }\n      }\n   n_objects = j;\n   if( !field_mode)\n      printf( \"%u objects after removing slow ones\\n\", (unsigned)n_objects);\n   else\n      max_revs_per_day = 20.;   /* for field-finding,  list everything */\n   rval = add_tle_to_obs( objects, n_objects, tle_file_name, search_radius,\n                                    max_revs_per_day);\n   if( rval)\n      fprintf( stderr, \"Couldn't open TLE file %s\\n\", tname);\n   else if( show_summary)\n      {\n      int n_matched = 0;\n      size_t n_unmatched = 0;\n\n      for( i = 0; (size_t)i < n_objects; i++)\n         if( objects[i].n_matches)        /* first show those that were IDed */\n            {\n            char buff[30];\n\n            printf( \"\\n%.12s \", objects[i].obs->text);\n            for( j = 0; j < (int)objects[i].n_matches; j++)\n               printf( \" %05d %s\", objects[i].matches[j].norad_number,\n                            unpack_intl( objects[i].matches[j].intl_desig, buff));\n            if( objects[i].n_matches == 1)\n               {\n               char *tptr = strchr( objects[i].matches[0].text, ':');\n\n               if( tptr)                   /* if only one match,  output */\n                  printf( \" %s\", tptr + 1);   /* the object name */\n               }\n            if( objects[i].n_matches)\n               n_matched++;\n            }\n      for( i = 0; (size_t)i < n_objects; i++)\n         if( !objects[i].n_matches)          /* now show those _not_ IDed */\n            {\n            if( !n_unmatched)\n               printf( \"\\nUnmatched objects :\");\n            printf( \"%s%.12s\", (n_unmatched % 5 == 0 ? \"\\n\" : \"\"), objects[i].obs->text);\n            n_unmatched++;\n            }\n      if( n_objects)\n         printf( \"\\n%d matched of %d tracklets (%.1f%%)\\n\", n_matched, (int)n_objects,\n                  (double)n_matched * 100. / (double)n_objects);\n      }\n   else if( field_mode)\n      {\n      const char *header =\n           \"Field        RA  (J2000) dec  '/min   PA   NORAD Int'l desig Name\";\n\n      printf( \"%s\", header);\n      for( i = 0; (size_t)i < n_objects; i++)\n         for( j = 0; j < (int)objects[i].n_matches; j++)\n            {\n            double ra = objects[i].matches[j].ra;\n            double dec = objects[i].matches[j].dec;\n            char buff[30];\n\n            epoch_of_date_to_j2000( objects[i].obs->jd, &ra, &dec);\n            printf( \"\\n%-12.12s %7.3f %7.3f %7.2f %5.1f\", objects[i].obs->text,\n                           ra * 180. / PI, dec * 180. / PI,\n                           objects[i].matches[j].motion_rate,\n                           objects[i].matches[j].motion_pa);\n            printf( \" %c %05d %-11s\",\n                         objects[i].matches[j].in_shadow ? '*' : ' ',\n                         objects[i].matches[j].norad_number,\n                         unpack_intl( objects[i].matches[j].intl_desig, buff));\n            printf( \"%s\", strchr( objects[i].matches[j].text, ':') + 1);\n            }\n      printf( \"\\n%s\", header);\n      }\n   if( output_astrometry_filename)\n      {\n      FILE *ofile = fopen( output_astrometry_filename, \"wb\");\n\n      if( !ofile)\n         {\n         fprintf( stderr, \"Couldn't open '%s' :\", output_astrometry_filename);\n         perror( NULL);\n         }\n      else\n         {\n         char buff[256];\n\n         fseek( ifile, 0L, SEEK_SET);\n         while( fgets( buff, sizeof( buff), ifile))\n            {\n            if( strlen( buff) > 80 && buff[80] < ' ')\n               {\n               char tbuff[30];\n               bool was_matched = false;\n\n               time_tag( buff + 59);\n               for( i = 0; (size_t)i < n_objects; i++)\n                  if( !memcmp( objects[i].obs->text, buff, 12))\n                     {\n                     if( objects[i].n_matches > 0\n                             && objects[i].matches[0].norad_number > 0)\n                        {\n                        const char *common_name =\n                                 strchr( objects[i].matches[0].text, ':') + 1;\n\n                        was_matched = true;\n                        unpack_intl( objects[i].matches[0].intl_desig, tbuff);\n                        if( add_new_line)\n                           if( !memcmp( tbuff + 5, \"999\", 3) || !memcmp( tbuff + 5, \"000\", 3))\n                              {\n                              fprintf( ofile, \"\\nCOM =%s   %05dU = %s\\n\",\n                                           common_name, objects[i].matches[0].norad_number,\n                                           tbuff);\n                              *tbuff = '\\0';\n                              }\n                        if( *tbuff)\n                           fprintf( ofile, \"%sCOM %05dU = %s   %s\\n\",\n                               (add_new_line ? \"\\n\" : \"\"),\n                               objects[i].matches[0].norad_number,\n                               tbuff, common_name);\n                        objects[i].matches[0].norad_number = -1;\n                        }\n                     }\n               if( was_matched && output_only_matches)\n                  fputs( buff, ofile);\n               }\n            if( !output_only_matches)\n               fputs( buff, ofile);\n            }\n         fclose( ofile);\n         }\n      }\n   fclose( ifile);\n   free( obs);\n   for( i = 0; (size_t)i < n_objects; i++)\n      if( objects[i].matches)\n         free( objects[i].matches);\n   free( objects);\n   get_station_code_data( NULL, NULL);\n   add_tle_to_obs( NULL, 0, NULL, 0., 0.);\n   printf( \"\\n%.1f seconds elapsed\\n\", (double)clock( ) / (double)CLOCKS_PER_SEC);\n   return( rval);\n}     /* End of main() */\n"
  },
  {
    "path": "sat_id.txt",
    "content": "-a(date) : only consider observations after (date)\n-b(date) : only consider observations before (date)\n-c       : check all TLEs\n-l(num)  : set \"lookahead\" warning on expiring TLEs (default=7 days)\n-m(num)  : ignore TLEs in orbits lower than (num) revs/day (default=6)\n-n(num)  : only look for NORAD ID (num)\n-o(filename)  : output astrometry with IDs added\n-r(num)  : set tolerance for computed-observed dist\n-t(filename)  : set input TLE file name\n-u            : show a summary of results\n-y(num)  : set tolerance for apparent motion mismatch\n-z(num)       : ignore objects slower than (num) arcmin/sec\n\n   -a(date) tells Sat_ID to ignore observations from after a certain\ndate;  -b(date) tells it to ignore observations from _before_ a\ncertain time.  The date format is flexible,  but something like\n\"-a2021Jan13\" is a good choice (four-digit year and month names\nmean there's no confusion about what's the date,  what's the month,\nand what's the year... dates such as \"11/09/08\" can lead to madness.)\n\n   For example,  I might check my large,  multi-year NEOCP archives\nfor just the objects found in late January 2021 by running\n\nsat_id neocp.old -a2021jan15 -b2021feb1\n\n   -c is basically for debugging purposes.  Normally,  Sat_ID gets its\nidea of which TLEs to check from the file 'tle_list.txt',  which gives\nit a long list of files.  And normally,  it'll ignore most of them;\ngive it some observations from 2021,  and it's bright enough to realize\nthat the TLEs for 2020 can be skipped.  -c causes Sat_ID to check those\nfiles anyway,  which sometimes lets me see my blunders (files that don't\nexist or are corrupted or don't actually contain TLEs.)\n\n   -l sets a \"lookahead\" time for expiring TLEs.  I can usually only\ncompute TLEs for just so far into the future.  If they're about to run\nout for a particular object in (by default) a week,  you get a warning\nmessage to that effect.  Except that on my own machine,  I run it with\n-l10,  so I'll know three days ahead of everybody else and can post\nupdated TLEs before they even get a warning.\n\n   On my own machine,  though,  I run it with -l10.  That way,  I'll know\nabout expiring TLEs three days ahead of everybody else and can post\nupdated TLEs before anybody else even sees a warning.  At least,  that's\nthe theory... once in a while,  I've been on vacation or something and\npeople have seen those errors.\n\n   -m(num) tells Sat_ID to neglect low-earth orbiters : anything making\nmore than (num) revolutions around the earth per day,  with (num)\ndefaulting to 6 (i.e.,  Sat_ID won't identify objects in orbits lower\nthan four hours).\n\n   -n(num) restricts output to the specified NORAD five-digit number.\nHandy when you're just wondering when/where object X got caught.\n\n   -o(filename) causes the input astrometry to be written to the\noutput file with COM lines specifying to what the objects were matched.\nThe COM lines,  just ahead of the first observation for each object,\nlook like\n\nCOM 44432U = 2019-040A\nCOM 37175U = 2010-050B\n\n   The above lines are a convenience for Find_Orb.  The first would tell\nit than the next object it finds is actually NORAD 44432 = 2019-040A\n= Spektr-RG;  it would \"remap\" the designation of the next observation\nit found to that.  The second would do the same thing,  except for\nNORAD 37175 = 2010-050B = Chang'e 2 booster.  This lets me avoid\nhaving to alter the original IDs in the astrometry,  but means that\nI want to compute an orbit for 2010-050B,  I'll get the data for it\ndespite different designations being used.\n\n   -r(num) says that the observed position of an object and the computed\nposition from a TLE are considered to be 'matching' if the positions are\nwithin (num) degrees.   Defaults to four degrees,  which is probably too\nlarge... but see above comments : even with the very loose cutoffs,  we\nrarely falsely ID a rock as an artsat.\n\n   -t(filename) resets the input TLE filename.  This defaults to\n~/tles/tle_list.txt.  But let's say I'm wondering what Space-Track can\nidentify without my help.  I might then set -t all_tle.txt,  where\n'all_tle.txt' is the master set of Space-Track TLEs.  Or I may have\ncreated some batch of TLEs for a new object,  and I'll run the MPC's\nITF file or my accumulated NEOCP observations against it to see if I\ncan spot any other finds of the new object.\n\n   -u causes Sat_ID to emit a \"summary\" at the end,  listing the\nobjects it found in the input file and any matches.\n\n   -y(num) says that the observed _motion_ of an object and the computed\n_motion_ from a TLE are considered to be a match if the motion is within\n20 arcseconds.  That is to say,  if the observations say the object\nmoved 1300 arcseconds in RA between first and last observation,  and\nthe TLEs compute a motion of 1319 arcsec over that time,  it is (just\nbarely) a match.\n\n   20 arcseconds may seem like a lot,  and I used to have it set much\nlower than that.  The problem is that if timing is off slightly,  or the\nobject fades in and out a bit,  you can easily have more of a motion\nmismatch than you'd expect.  And in general,  setting this 'loose'\ntolerance only rarely results in objects being falsely identified as\nartsats.\n\n   -z(speed) sets a lower limit on speed.  By default,  anything\nmoving slower than 0.001'/sec is simply tossed out as too slow to\npossibly be an artsat;  that would be -z.001.  As with the -y option\ndescribed above,  this default is probably excessively low (could result\nin mistakenly identifying some rocks as artsats).  But -- as with that\noption -- it appears to be unusual for such false detections to occur.\nI originally set both limts them to be tighter,  but I found that Sat_ID\nwould occasionally fail to identify real artsats. Generally speaking,\nit's hard for a rock to move so fast,  and in the same direction and\npart of the sky,  as an artsat that it would fool you.\n\n   Note that this comes from the standpoint of a guy fed a steady\ndiet of asteroid data,  trying to filter out the occasional artsat.\nIf you are deliberately targeting artsats and only rarely seeing\nrocks, you could set a still lower speed limit with -z,  and a higher\nlimit on observed motion (-y),  _and_ a higher position mismatch\ntolerance (-r),  and not get a flood of rocks misidentified as\nartsats.  In fact,  I do set looser limits when examining the NEOCP;\nI get some rocks misidentified as artsats as a result,  which I then\nam usually able to rubbish.  But those looser limits also allow me to\noccasionally say \"this really is an artsat; it's just wandered\nfurther than expected from where the TLEs expected it to be.\"\n"
  },
  {
    "path": "sat_id2.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#ifdef __has_include\n   #if __has_include(<cgi_func.h>)\n       #include \"cgi_func.h\"\n   #else\n       #error   \\\n         'cgi_func.h' not found.  This project depends on the 'lunar'\\\n         library.  See www.github.com/Bill-Gray/lunar .\\\n         Clone that repository,  'make'  and 'make install' it.\n   #endif\n#else\n   #include \"cgi_func.h\"\n#endif\n#include \"watdefs.h\"\n#include \"stringex.h\"\n\nint sat_id_main( const int argc, const char **argv);\n\nint main( const int unused_argc, const char **unused_argv)\n{\n   const char *argv[20];\n   const size_t max_buff_size = 400000;       /* room for 5000 obs */\n   char *buff = (char *)malloc( max_buff_size), *tptr;\n   char field[30];\n   const char *temp_obs_filename = \"sat_obs.txt\";\n   double search_radius = 4.;     /* look 4 degrees for matches */\n   double motion_cutoff = 60.;  /* up to 60\" discrepancy OK */\n   double low_speed_cutoff = 0.001;  /* anything slower than this is almost */\n   int argc = 0;                     /* certainly not an artsat */\n   FILE *lock_file = fopen( \"lock.txt\", \"w\");\n   size_t bytes_written = 0, i;\n   int cgi_status, show_summary = 0;\n   extern int verbose;\n#ifndef _WIN32\n   extern char **environ;\n\n   avoid_runaway_process( 15);\n#endif         /* _WIN32 */\n   setbuf( lock_file, NULL);\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argc);\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argv);\n   printf( \"Content-type: text/html\\n\\n\");\n   printf( \"<html> <body> <pre>\\n\");\n   if( !lock_file)\n      {\n      printf( \"<p> Server is busy.  Try again in a minute or two. </p>\");\n      printf( \"<p> Your orbit is very important to us! </p>\");\n      return( 0);\n      }\n   fprintf( lock_file, \"We're in\\n\");\n#ifndef _WIN32\n   for( i = 0; environ[i]; i++)\n      fprintf( lock_file, \"%s\\n\", environ[i]);\n#endif\n   cgi_status = initialize_cgi_reading( );\n   fprintf( lock_file, \"CGI status %d\\n\", cgi_status);\n   if( cgi_status <= 0)\n      {\n      printf( \"<p> <b> CGI data reading failed : error %d </b>\", cgi_status);\n      printf( \"This isn't supposed to happen.</p>\\n\");\n      return( 0);\n      }\n   while( !get_cgi_data( field, buff, NULL, max_buff_size))\n      {\n      fprintf( lock_file, \"Field '%s'\\n\", field);\n      if( !strcmp( field, \"TextArea\") || !strcmp( field, \"upfile\"))\n         {\n         if( strlen( buff) > 70)\n            {\n            FILE *ofile = fopen( temp_obs_filename,\n                               (bytes_written ? \"ab\" : \"wb\"));\n\n            fprintf( lock_file, \"File opened : %p\\n\", (void *)ofile);\n            if( !ofile)\n               {\n               printf( \"<p> Couldn't open %s : %s </p>\\n\", temp_obs_filename, strerror( errno));\n               fprintf( lock_file, \"Couldn't open %s: %s\\n\", temp_obs_filename, strerror( errno));\n               return( -1);\n               }\n            bytes_written += fwrite( buff, 1, strlen( buff), ofile);\n            fclose( ofile);\n            }\n         }\n      if( !strcmp( field, \"radius\"))\n         {\n         const char *verbosity = strchr( buff, 'v');\n\n         search_radius = atof( buff);\n         if( verbosity)\n            verbose = atoi( verbosity + 1) + 1;\n         }\n      if( !strcmp( field, \"motion\"))\n         motion_cutoff = atof( buff);\n      if( !strcmp( field, \"low_speed\"))\n         low_speed_cutoff = atof( buff);\n      if( !strcmp( field, \"summary\"))\n         show_summary = 1;\n      }\n   fprintf( lock_file, \"Fields read\\n\");\n// printf( \"<p>Fields read</p>\\n\");\n   if( verbose)\n      printf( \"Searching to %f degrees;  %u bytes read from input\\n\",\n                     search_radius, (unsigned)bytes_written);\n   argv[argc++] = \"sat_id\";\n   argv[argc++] = temp_obs_filename;\n   argv[argc++] = \"-t../../tles/tle_list.txt\";\n   snprintf_err( field, sizeof( field), \"-r%.2f\", search_radius);\n   argv[argc++] = field;\n   snprintf_err( buff, max_buff_size, \"-y%f\", motion_cutoff);\n   argv[argc++] = buff;\n   tptr = buff + strlen( buff) + 1;\n   snprintf( tptr, 15, \"-z%f\", low_speed_cutoff);\n   argv[argc++] = tptr;\n   if( show_summary)\n      argv[argc++] = \"-u\";\n   argv[argc] = NULL;\n   for( i = 0; argv[i]; i++)\n      fprintf( lock_file, \"arg %d: '%s'\\n\", (int)i, argv[i]);\n   sat_id_main( argc, argv);\n   fprintf( lock_file, \"sat_id_main called\\n\");\n   free( buff);\n   printf( \"On-line Sat_ID compiled \" __DATE__ \" \" __TIME__ \" UTC-5h\\n\");\n   printf( \"See <a href='https://www.github.com/Bill-Gray/sat_code'>\"\n               \"https://www.github.com/Bill-Gray/sat_code</a> for source code\\n\");\n   printf( \"</pre> </body> </html>\");\n   return( 0);\n}\n"
  },
  {
    "path": "sat_id3.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#ifdef __has_include\n   #if __has_include(<cgi_func.h>)\n       #include \"cgi_func.h\"\n   #else\n       #error   \\\n         'cgi_func.h' not found.  This project depends on the 'lunar'\\\n         library.  See www.github.com/Bill-Gray/lunar .\\\n         Clone that repository,  'make'  and 'make install' it.\n   #endif\n#else\n   #include \"cgi_func.h\"\n#endif\n#include \"watdefs.h\"\n#include \"stringex.h\"\n\n/* This is the code behind\n\nhttps://www.projectpluto.com/sat_img.htm\n\n   It takes the input data,  creates a temporary file containing a\nsingle 'image field data' line,  then uses the Sat_ID code (see sat_id.cpp)\nto figure out which artsats are in that field.  */\n\nint sat_id_main( const int argc, const char **argv);\n\nint main( const int unused_argc, const char **unused_argv)\n{\n   const char *argv[20];\n   char buff[80];\n   char field[30];\n   char search_radius[10], date[50], latitude[30], longitude[30];\n   char altitude[10], ra[20], dec[20];\n   const int argc = 3;\n   const char *output_file_name = \"field.txt\";\n   FILE *ofile;\n   FILE *lock_file = fopen( \"lock.txt\", \"w\");\n   size_t i;\n   int cgi_status;\n#ifndef _WIN32\n   extern char **environ;\n\n   avoid_runaway_process( 15);\n#endif         /* _WIN32 */\n   setbuf( lock_file, NULL);\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argc);\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argv);\n   printf( \"Content-type: text/html\\n\\n\");\n   printf( \"<html> <body> <pre>\\n\");\n   if( !lock_file)\n      {\n      printf( \"<p> Server is busy.  Try again in a minute or two. </p>\");\n      printf( \"<p> Your orbit is very important to us! </p>\");\n      return( 0);\n      }\n   fprintf( lock_file, \"We're in\\n\");\n#ifndef _WIN32\n   for( i = 0; environ[i]; i++)\n      fprintf( lock_file, \"%s\\n\", environ[i]);\n#endif\n   cgi_status = initialize_cgi_reading( );\n   fprintf( lock_file, \"CGI status %d\\n\", cgi_status);\n   if( cgi_status <= 0)\n      {\n      printf( \"<p> <b> CGI data reading failed : error %d </b>\", cgi_status);\n      printf( \"This isn't supposed to happen.</p>\\n\");\n      return( 0);\n      }\n   *search_radius = *date = *latitude = *longitude = *altitude = '\\0';\n   *ra = *dec = '\\0';\n   while( !get_cgi_data( field, buff, NULL, sizeof( buff)))\n      {\n      fprintf( lock_file, \"Field '%s'\\n\", field);\n      if( !strcmp( field, \"radius\"))\n         {\n         strlcpy( search_radius, buff, sizeof( search_radius));\n         if( !atof( search_radius))\n            printf( \"Search radius must be non-zero\\n\");\n         }\n      else if( !strcmp( field, \"time\"))\n         strlcpy( date, buff, sizeof( date));\n      else if( !strcmp( field, \"lat\"))\n         strlcpy( latitude, buff, sizeof( latitude));\n      else if( !strcmp( field, \"lon\"))\n         strlcpy( longitude, buff, sizeof( longitude));\n      else if( !strcmp( field, \"alt\"))\n         strlcpy( altitude, buff, sizeof( altitude));\n      else if( !strcmp( field, \"ra\"))\n         strlcpy( ra, buff, sizeof( ra));\n      else if( !strcmp( field, \"dec\"))\n         strlcpy( dec, buff, sizeof( dec));\n      }\n   ofile = fopen( output_file_name, \"w\");\n   fprintf( ofile, \"COD XXX\\n\");\n   fprintf( ofile, \"COM Long. %s, Lat. %s, Alt. %s, unspecified\\n\",\n                  longitude, latitude, altitude);\n   fprintf( ofile, \"Field,%s,%s,%s,XXX\\n\", date, ra, dec);\n   fclose( ofile);\n   argv[0] = \"sat_id\";\n   argv[1] = output_file_name;\n   argv[2] = \"-t../../tles/tle_list.txt\";\n   argv[3] = NULL;\n   for( i = 0; argv[i]; i++)\n      fprintf( lock_file, \"arg %d: '%s'\\n\", (int)i, argv[i]);\n   sat_id_main( argc, argv);\n   fprintf( lock_file, \"sat_id_main called\\n\");\n   printf( \"On-line artsats-in-field-finder compiled\"\n                            __DATE__ \" \" __TIME__ \" UTC-5h\\n\");\n   printf( \"See <a href='https://www.github.com/Bill-Gray/sat_code'>\"\n               \"https://www.github.com/Bill-Gray/sat_code</a> for source code\\n\");\n   printf( \"</pre> </body> </html>\");\n   return( 0);\n}\n"
  },
  {
    "path": "sat_util.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"sat_util.h\"\n\nchar *fgets_trimmed( char *buff, const int buffsize, FILE *ifile)\n{\n   char *rval = fgets( buff, buffsize, ifile);\n\n   if( rval)\n      {\n      size_t i = 0;\n\n      while( rval[i] != 10 && rval[i] != 13 && rval[i])\n         i++;\n      while( i && rval[i - 1] == ' ')\n         i--;        /* drop trailing spaces */\n      rval[i] = '\\0';\n      }\n   return( rval);\n}\n\n#if !defined( _WIN32)\nvoid make_config_dir_name( char *oname, const char *iname)\n{\n#ifdef ON_LINE_VERSION\n   strcpy( oname, getenv( \"DOCUMENT_ROOT\"));\n   strcat( oname, \"/\");\n#else\n   const char *home_dir = getenv( \"HOME\");\n\n   if( home_dir)\n      {\n      strcpy( oname, home_dir);\n      strcat( oname, \"/.find_orb/\");\n      }\n   else\n      *oname = '\\0';\n#endif\n   strcat( oname, iname);\n}\n\nFILE *local_then_config_fopen( const char *filename, const char *permits)\n{\n   FILE *rval = fopen( filename, permits);\n\n   if( !rval)\n      {\n      char ext_filename[255];\n\n      make_config_dir_name( ext_filename, filename);\n      rval = fopen( ext_filename, \"rb\");\n      }\n   return( rval);\n}\n#else\nFILE *local_then_config_fopen( const char *filename, const char *permits)\n{\n   return( fopen( filename, permits));\n}\n#endif\n"
  },
  {
    "path": "sat_util.h",
    "content": "#ifndef SAT_UTIL_H_INCLUDED\n#define SAT_UTIL_H_INCLUDED\n\n/* A few functions that are used in common by sat_id, sat_id2, and sat_eph. */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif /* #ifdef __cplusplus */\n\nFILE *local_then_config_fopen( const char *filename, const char *permits);\nchar *fgets_trimmed( char *buff, const int buffsize, FILE *ifile);\n\n#if !defined( _WIN32)\nvoid make_config_dir_name( char *oname, const char *iname);\n#endif\n\n#ifdef __cplusplus\n}\n#endif  /* #ifdef __cplusplus */\n#endif  /* #ifndef SAT_UTIL_H_INCLUDED */\n"
  },
  {
    "path": "sdp4.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stddef.h>\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#include <stdio.h>\n\n/* For high satellites,  we do a numerical integration that uses a\nrather drastic set of simplifications.  We include the earth,\nmoon,  and sun,  but with low-precision approximations for the\npositions of those last two.  References are to Meeus' _Astronomical\nAlgorithms_,  2nd edition.  Results are in meters from the center\nof the earth,  in ecliptic coordinates of date.\n\nThe numerical integration is done using the 'classic' RK4 algorithm,\nas described at (for example)\n\nhttps://en.wikipedia.org/wiki/Runge–Kutta_methods\n\nThe solar and lunar positions are computed using Meeus' formulae,  which\nare a little computationally intensive.  RK4 has the slight advantage of\nrequiring us to compute lunar/solar positions only for the steps\nthemselves and their midpoints.\n\nYou probably know that the 'elements' for traditional TLEs are fitted to\nthe SGP4 and SDP4 models : if you tried to numerically integrate TLEs\nusing a more sophisticated model,  you'd actually get _worse_ results.\nSimilarly,  the state vectors for my modified TLEs are integrated using\nthe following model,  which tries to balance accuracy and speed.  Just as\nyou shouldn't try to \"improve\" SGP4/SDP4,  you shouldn't try to \"improve\"\nthe following;  it'll only break backward compatibility.  */\n\nstatic void raw_lunar_solar_position( const double jd, double *lunar_xyzr, double *solar_xyzr)\n{\n   const double j2000 = 2451545;    /* 1.5 Jan 2000 = JD 2451545 */\n   const double t_cen = (jd - j2000) / 36525.;\n            /* Mean lunar longitude, (47.1) */\n   const double l_prime = 218.3164477 * pi / 180.\n                        + (481267.88123421 * pi / 180.) * t_cen;\n            /* Lunar mean anomaly,  (47.4) */\n   const double m_prime = 134.9633964 * pi / 180.\n                        + (477198.8675055 * pi / 180.) * t_cen;\n            /* Solar mean longitude, (25.2) */\n   const double l_solar = 280.46646 * pi / 180.\n                        + (36000.76983 * pi / 180.) * t_cen;\n            /* Solar mean anomaly, (47.3)  */\n   const double m_solar = 357.5291092 * pi / 180.\n                        + (35999.0502909 * pi / 180.) * t_cen;\n            /* Lunar mean argument of latitude (47.5) */\n   const double f = 93.2720950 * pi / 180.\n                        + (483202.0175233 * pi / 180.) * t_cen;\n   const double lunar_mean_elong = (297.8501921 * pi / 180.)\n                        + (445267.1114034 * pi / 180.) * t_cen;\n   const double term2 = 2. * lunar_mean_elong - m_prime;\n   const double lunar_lon = l_prime   /* See table 47.A */\n                        + (6.288774 * pi / 180.) * sin( m_prime)\n                        + (1.274027 * pi / 180.) * sin( term2)\n                        + (0.658314 * pi / 180.) * sin( 2. * lunar_mean_elong)\n                        + (0.213618 * pi / 180.) * sin( 2. * m_prime)\n                        - (0.185166 * pi / 180.) * sin( m_solar)\n                        - (0.114332 * pi / 180.) * sin( 2. * f);\n   const double lunar_lat = (5.128122 * pi / 180.) * sin( f)\n                          + (0.280602 * pi / 180.) * sin( m_prime + f)\n                          + (0.277693 * pi / 180.) * sin( m_prime - f)\n                          + (0.173237 * pi / 180.) * sin( 2. * lunar_mean_elong - f);\n   const double lunar_r = 385000560.       /* in meters */\n                         - 20905355. * cos( m_prime)\n                         -  3699111. * cos( term2)\n                         -  2955968  * cos( 2. * lunar_mean_elong)\n                         -   569925  * cos( 2. * m_solar);\n   const double solar_ecc = 0.016708634;     /* (25.4) */\n   const double solar_lon = l_solar          /* (above (25.5)) */\n                        + (1.914602 * pi / 180.) * sin( m_solar);\n   const double au_in_meters = 1.495978707e+11;\n   const double solar_r = au_in_meters * (1. - solar_ecc * cos( m_solar));\n   double tval;\n\n   tval = lunar_r * cos( lunar_lat);\n   *lunar_xyzr++ = tval * cos( lunar_lon);\n   *lunar_xyzr++ = tval * sin( lunar_lon);\n   *lunar_xyzr++ = lunar_r * sin( lunar_lat);\n   *lunar_xyzr++ = lunar_r;\n\n   *solar_xyzr++ = solar_r * cos( solar_lon);\n   *solar_xyzr++ = solar_r * sin( solar_lon);\n   *solar_xyzr++ = 0.;\n   *solar_xyzr++ = solar_r;\n}\n\n/* For the RK4 integration,  we're frequently asking for the sun and\nmoon positions at the exact same time we needed for the preceding step.\nCaching those positions saves recomputing them. */\n\nvoid DLL_FUNC lunar_solar_position( const double jd,\n                    double *lunar_xyzr, double *solar_xyzr)\n{\n   static double curr_jd = 0., lunar[4], solar[4];\n   size_t i;\n\n   if( curr_jd != jd)\n      {\n      curr_jd = jd;\n      raw_lunar_solar_position( jd, lunar, solar);\n      }\n   for( i = 0; i < 4; i++)\n      {\n      if( lunar_xyzr)\n         lunar_xyzr[i] = lunar[i];\n      if( solar_xyzr)\n         solar_xyzr[i] = solar[i];\n      }\n}\n\nstatic const double sin_obliq_2000 = 0.397777155931913701597179975942380896684;\nstatic const double cos_obliq_2000 = 0.917482062069181825744000384639406458043;\n\nstatic void equatorial_to_ecliptic( double *vect)\n{\n   double temp;\n\n   temp    = vect[2] * cos_obliq_2000 - vect[1] * sin_obliq_2000;\n   vect[1] = vect[1] * cos_obliq_2000 + vect[2] * sin_obliq_2000;\n   vect[2] = temp;\n}\n\nstatic void ecliptic_to_equatorial( double *vect)\n{\n   double temp;\n\n   temp    = vect[2] * cos_obliq_2000 + vect[1] * sin_obliq_2000;\n   vect[1] = vect[1] * cos_obliq_2000 - vect[2] * sin_obliq_2000;\n   vect[2] = temp;\n}\n\nstatic void init_high_ephemeris( double *params, const tle_t *tle)\n{\n   const double *state_vect = &tle->xincl;   /* position at epoch,  in meters */\n   size_t i;\n\n   for( i = 0; i < 6; i++)\n      params[i] = state_vect[i];\n   equatorial_to_ecliptic( params);\n   equatorial_to_ecliptic( params + 3);\n}\n\n#define c1           params[2]\n#define c4           params[3]\n#define xnodcf       params[4]\n#define t2cof        params[5]\n#define deep_arg     ((deep_arg_t *)( params + 10))\n\nvoid DLL_FUNC SDP4_init( double *params, const tle_t *tle)\n{\n   init_t init;\n\n   if( tle->ephemeris_type == 'H')\n      {\n      init_high_ephemeris( params, tle);\n      return;\n      }\n   sxpx_common_init( params, tle, &init, deep_arg);\n   deep_arg->sing = sin(tle->omegao);\n   deep_arg->cosg = cos(tle->omegao);\n\n   /* initialize Deep() */\n   Deep_dpinit( tle, deep_arg);\n#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH\n   /* initialize lunisolar perturbations: */\n   deep_arg->t = 0.;                            /* added 30 Dec 2003 */\n   deep_arg->solar_lunar_init_flag = 1;\n   Deep_dpper( tle, deep_arg);\n   deep_arg->solar_lunar_init_flag = 0;\n#endif\n} /*End of SDP4() initialization */\n\nstatic inline double vector_len( const double *vect)\n{\n   double len2 = vect[0] * vect[0] + vect[1] * vect[1] + vect[2] * vect[2];\n\n   return( sqrt( len2));\n}\n\n/* Input position is in meters,  accel is in m/sec^2 */\n\nstatic int calc_accel( const double jd, const double *pos, double *accel)\n{\n   size_t i;\n   const double earth_gm = 3.9860044e+14;   /* in m^3/s^2 */\n   const double solar_gm = 1.3271243994e+20;   /* m^3/s^2 */\n   const double lunar_gm = 4.902798e+12;       /* m^3/s^2 */\n   double r = vector_len( pos);\n   double accel_factor = -earth_gm / (r * r * r);\n   double lunar_xyzr[4], solar_xyzr[4];\n   unsigned obj_idx;\n\n   for( i = 0; i < 3; i++)\n      accel[i] = accel_factor * pos[i];\n   lunar_solar_position( jd, lunar_xyzr, solar_xyzr);\n   for( obj_idx = 0; obj_idx < 2; obj_idx++)\n      {\n      double *opos = (obj_idx ? lunar_xyzr : solar_xyzr);\n      double delta[3], d;\n      const double gm = (obj_idx ? lunar_gm : solar_gm);\n      double accel_factor2;\n\n      accel_factor = gm / (opos[3] * opos[3] * opos[3]);\n      for( i = 0; i < 3; i++)\n         delta[i] = opos[i] - pos[i];\n      d = vector_len( delta);\n      accel_factor2 = gm / (d * d * d);\n      for( i = 0; i < 3; i++)\n         accel[i] -= accel_factor * opos[i] - accel_factor2 * delta[i];\n      }\n   return( 0);\n}\n\nstatic int calc_state_vector_deriv( const double jd,\n                       const double state_vect[6], double deriv[6])\n{\n   deriv[0] = state_vect[3];\n   deriv[1] = state_vect[4];\n   deriv[2] = state_vect[5];\n   return( calc_accel( jd, state_vect, deriv + 3));\n}\n\n/* NOTE: t_since is in minutes,  posn is in km, vel is in km/minutes.\nState vector is in meters and m/s.  Hence some conversions...\n\n'high_ephemeris()' does the actual RK4 numerical integration,  using a\nsimplified model of the earth and moon and a quite basic integration\nstep size adjustment so that it can take small steps when the object is\nclose to the earth or moon and larger steps when far away.  As described\nabove,  any temptation to \"improve\" the integration should be resisted. */\n\nstatic int high_ephemeris( double tsince, const tle_t *tle, const double *params,\n                                         double *pos, double *vel)\n{\n   const double meters_per_km = 1000.;\n   const double seconds_per_minute = 60.;\n   const double seconds_per_day =\n               seconds_per_minute * minutes_per_day;     /* a.k.a. 86400 */\n   size_t i, j;\n   double jd = tle->epoch, state_vect[6];\n\n   for( i = 0; i < 6; i++)\n      state_vect[i] = params[i];\n   tsince /= minutes_per_day;       /* input was in minutes;  days are */\n   while( tsince)                   /* more convenient hereforth       */\n      {\n      double dt = tsince, dt_in_seconds;\n      double max_step = 1.;\n      double kvects[4][6];\n\n      calc_state_vector_deriv( jd, state_vect, kvects[0]);\n      for( j = 3; j < 6; j++)\n         if( max_step > 1e-3 / fabs( kvects[0][j]))\n            max_step = 1e-3 / fabs( kvects[0][j]);\n      if( max_step < 1e-5)\n         max_step = 1e-5;\n      if( tsince > max_step)\n         dt = max_step;\n      else if( tsince < -max_step)\n         dt = -max_step;\n      dt_in_seconds = dt * seconds_per_day;\n      for( j = 1; j < 4; j++)\n         {\n         const double step = (j == 3 ? dt_in_seconds : dt_in_seconds * .5);\n         double tstate[6];\n\n         for( i = 0; i < 6; i++)\n            tstate[i] = state_vect[i] + step * kvects[j - 1][i];\n         calc_state_vector_deriv( jd + (j == 3 ? dt : dt / 2.),\n                        tstate, kvects[j]);\n         }\n\n      for( i = 0; i < 6; i++)\n         state_vect[i] += (dt_in_seconds / 6.) *\n               (kvects[0][i] + 2. * (kvects[1][i] + kvects[2][i]) + kvects[3][i]);\n      jd += dt;\n      tsince -= dt;\n      }\n   for( i = 0; i < 3; i++)\n      {\n      pos[i] = state_vect[i];\n      vel[i] = state_vect[i + 3];\n      }\n   ecliptic_to_equatorial( vel);\n   ecliptic_to_equatorial( pos);\n                  /* Now,  cvt meters to km, meters/second to km/minute: */\n   for( i = 0; i < 3; i++)\n      {\n      pos[i] /= meters_per_km;\n      vel[i] *= seconds_per_minute / meters_per_km;\n      }\n   return( 0);\n}\n\nint DLL_FUNC SDP4( const double tsince, const tle_t *tle, const double *params,\n                                         double *pos, double *vel)\n{\n  double\n      a, tempa, tsince_squared,\n      xl, xnoddf;\n\n   if( tle->ephemeris_type == 'H')\n      {\n      double unused_vel[3];\n\n      return( high_ephemeris( tsince, tle, params, pos, (vel ? vel : unused_vel)));\n      }\n  /* Update for secular gravity and atmospheric drag */\n  deep_arg->omgadf = tle->omegao + deep_arg->omgdot * tsince;\n  xnoddf = tle->xnodeo + deep_arg->xnodot * tsince;\n  tsince_squared = tsince*tsince;\n  deep_arg->xnode = xnoddf + xnodcf * tsince_squared;\n  deep_arg->xn = deep_arg->xnodp;\n\n  /* Update for deep-space secular effects */\n  deep_arg->xll = tle->xmo + deep_arg->xmdot * tsince;\n  deep_arg->t = tsince;\n\n  Deep_dpsec( tle, deep_arg);\n\n  tempa = 1-c1*tsince;\n  if( deep_arg->xn < 0.)\n     return( SXPX_ERR_NEGATIVE_XN);\n  a = pow(xke/deep_arg->xn,two_thirds)*tempa*tempa;\n  deep_arg->em -= tle->bstar*c4*tsince;\n\n  /* Update for deep-space periodic effects */\n  deep_arg->xll += deep_arg->xnodp * t2cof * tsince_squared;\n\n  Deep_dpper( tle, deep_arg);\n\n            /* Keeping xinc positive is not really necessary,  unless        */\n            /* you're displaying elements and dislike negative inclinations. */\n#ifdef KEEP_INCLINATION_POSITIVE\n  if (deep_arg->xinc < 0.)       /* Begin April 1983 errata correction: */\n     {\n     deep_arg->xinc = -deep_arg->xinc;\n     deep_arg->sinio = -deep_arg->sinio;\n     deep_arg->xnode += pi;\n     deep_arg->omgadf -= pi;\n     }                          /* End April 1983 errata correction. */\n#endif\n\n  xl = deep_arg->xll + deep_arg->omgadf + deep_arg->xnode;\n               /* Dundee change:  Reset cosio,  sinio for new xinc: */\n  deep_arg->cosio = cos( deep_arg->xinc);\n  deep_arg->sinio = sin( deep_arg->xinc);\n\n  return( sxpx_posn_vel( deep_arg->xnode, a, deep_arg->em, deep_arg->cosio,\n                deep_arg->sinio, deep_arg->xinc, deep_arg->omgadf,\n                xl, pos, vel));\n} /* SDP4 */\n"
  },
  {
    "path": "sdp8.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#define tthmun              params[0]\n#define sini2               params[1]\n#define cosi2               params[2]\n#define unm5th              params[3]\n#define unmth2              params[4]\n#define xmdt1               params[5]\n#define xgdt1               params[6]\n#define xhdt1               params[7]\n#define xndt                params[8]\n#define edot                params[9]\n\nvoid sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg);\nvoid sxp8_common_init( double *params, const tle_t *tle, deep_arg_t *deep_arg);\n\nvoid DLL_FUNC SDP8_init( double *params, const tle_t *tle)\n{\n   const double rho = .15696615;\n   const double b = tle->bstar*2./rho;\n   double\n         alpha2, b1, b2, b3, c0,\n         c1, c4, c5, cos2g, d1, d2, d3, d4,\n         d5, eeta, eta, eta2,\n         po, psim2, r1, tsi, xndtn;\n   deep_arg_t *deep_arg = ((deep_arg_t *)( params + 10));\n\n   sxpall_common_init( tle, deep_arg);\n   sxp8_common_init( params, tle, deep_arg);\n   deep_arg->sinio = sin( tle->xincl);\n\n   /* Initialization */\n   po = deep_arg->aodp*deep_arg->betao2;\n   tsi = 1./(po-s_const);\n   eta = tle->eo*s_const*tsi;\n   eta2 = eta * eta;\n   psim2 = (r1 = 1./(1.-eta2), fabs(r1));\n   alpha2 = deep_arg->eosq+1.;\n   eeta = tle->eo*eta;\n   cos2g = deep_arg->cosg * deep_arg->cosg * 2. - 1.;\n   d5 = tsi*psim2;\n   d1 = d5/po;\n   d2 = eta2*(eta2*4.5+36.)+12.;\n   d3 = eta2*(eta2*2.5+15.);\n   d4 = eta*(eta2*3.75+5.);\n   b1 = ck2*tthmun;\n   b2 = -ck2*unmth2;\n   b3 = a3ovk2*deep_arg->sinio;\n   r1 = tsi, r1 *= r1;\n   c0 = b*.5*rho*qoms2t*deep_arg->xnodp*deep_arg->aodp*\n            (r1*r1)*pow( psim2, 3.5)/sqrt(alpha2);\n   r1 = alpha2;\n   c1 = deep_arg->xnodp*1.5*(r1*r1)*c0;\n   c4 = d1*d3*b2;\n   c5 = d5*d4*b3;\n   xndt = c1*(eta2*(deep_arg->eosq*34.+3.)+2.+eeta*5.*(eta2+4.)+\n     deep_arg->eosq*8.5+d1*d2*b1+c4*cos2g+c5*deep_arg->sing);\n   xndtn = xndt/deep_arg->xnodp;\n   edot = -two_thirds*xndtn*(1.-tle->eo);\n\n   /* initialize Deep() */\n   Deep_dpinit( tle, deep_arg);\n#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH\n   /* initialize lunisolar perturbations: */\n   deep_arg->t = 0.;                            /* added 30 Dec 2003 */\n   deep_arg->solar_lunar_init_flag = 1;\n   Deep_dpper( tle, deep_arg);\n   deep_arg->solar_lunar_init_flag = 0;\n#endif\n} /* End of SDP8() initialization */\n\nint DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *params,\n                                double *pos, double *vel)\n{\n   double\n        am, aovr, axnm, aynm, beta, beta2m,\n        cose, cosos, cs2f2g, csf, csfg, cslamb, di,\n        diwc, dr, ecosf, fm, g1, g10, g13, g14, g2,\n        g3, g4, g5, pm, r1, rdot, rm, rr, rvdot, sine,\n        sinos, sn2f2g, snf, snfg, sni2du, sinio2,\n        snlamb, temp, ux, uy, uz, vx, vy, vz, xlamb,\n        xmam, xmamdf, y4, y5, z1, z7, zc2, zc5;\n  int i;\n  deep_arg_t *deep_arg = ((deep_arg_t *)( params + 10));\n\n  /* Update for secular gravity and atmospheric drag */\n  z1 = xndt*.5*tsince*tsince;\n  z7 = two_thirds*3.5*z1/deep_arg->xnodp;\n  xmamdf = tle->xmo+deep_arg->xmdot*tsince;\n  deep_arg->omgadf = tle->omegao+deep_arg->omgdot*tsince+z7*xgdt1;\n  deep_arg->xnode = tle->xnodeo+deep_arg->xnodot*tsince+z7*xhdt1;\n  deep_arg->xn = deep_arg->xnodp;\n\n  /* Update for deep-space secular effects */\n  deep_arg->xll = xmamdf;\n  deep_arg->t = tsince;\n  Deep_dpsec( tle, deep_arg);\n  xmamdf = deep_arg->xll;\n  deep_arg->xn += xndt*tsince;\n  deep_arg->em += edot*tsince;\n  xmam = xmamdf+z1+z7*xmdt1;\n\n  /* Update for deep-space periodic effects */\n  deep_arg->xll = xmam;\n  Deep_dpper( tle, deep_arg);\n  xmam = deep_arg->xll;\n  xmam = FMod2p(xmam);\n\n  /* Solve Kepler's equation */\n  zc2 = xmam+deep_arg->em*sin(xmam)*(deep_arg->em*cos(xmam)+1.);\n\n  i = 0;\n  do\n    {\n      double cape;\n\n      sine = sin(zc2);\n      cose = cos(zc2);\n      zc5 = 1./(1.-deep_arg->em*cose);\n      cape = (xmam+deep_arg->em*sine-zc2)*zc5+zc2;\n      r1 = cape-zc2;\n      if (fabs(r1) <= e6a) break;\n      zc2 = cape;\n    }\n  while(i++ < 10);\n\n  /* Short period preliminary quantities */\n  am = pow( xke / deep_arg->xn, two_thirds);\n  beta2m = 1.f-deep_arg->em*deep_arg->em;\n  sinos = sin(deep_arg->omgadf);\n  cosos = cos(deep_arg->omgadf);\n  axnm = deep_arg->em*cosos;\n  aynm = deep_arg->em*sinos;\n  pm = am*beta2m;\n  g1 = 1./pm;\n  g2 = ck2*.5*g1;\n  g3 = g2*g1;\n  beta = sqrt(beta2m);\n  g4 = a3ovk2*.25*deep_arg->sinio;\n  g5 = a3ovk2*.25*g1;\n  snf = beta*sine*zc5;\n  csf = (cose-deep_arg->em)*zc5;\n  fm = atan2(snf, csf);\n  if( fm < 0.)\n     fm += pi + pi;\n  snfg = snf*cosos+csf*sinos;\n  csfg = csf*cosos-snf*sinos;\n  sn2f2g = snfg*2.*csfg;\n  r1 = csfg;\n  cs2f2g = r1*r1*2.-1.;\n  ecosf = deep_arg->em*csf;\n  g10 = fm-xmam+deep_arg->em*snf;\n  rm = pm/(ecosf+1.);\n  aovr = am/rm;\n  g13 = deep_arg->xn*aovr;\n  g14 = -g13*aovr;\n  dr = g2*(unmth2*cs2f2g-tthmun*3.)-g4*snfg;\n  diwc = g3*3.*deep_arg->sinio*cs2f2g-g5*aynm;\n  di = diwc*deep_arg->cosio;\n  sinio2 = sin(deep_arg->xinc*.5);\n\n  /* Update for short period periodics */\n  sni2du = sini2*(g3*((1.-deep_arg->cosio2*7.)*.5*sn2f2g-unm5th*\n      3.*g10)-g5*deep_arg->sinio*csfg*(ecosf+2.))-g5*.5*\n           deep_arg->cosio2*axnm/cosi2;\n  xlamb = fm+deep_arg->omgadf+deep_arg->xnode+g3*((deep_arg->cosio*6.+\n     1.-deep_arg->cosio2*7.)*.5*sn2f2g-(unm5th+deep_arg->cosio*2.)*\n     3.*g10)+g5*deep_arg->sinio*(deep_arg->cosio*axnm/\n     (deep_arg->cosio+1.)-(ecosf+2.)*csfg);\n  y4 = sinio2*snfg+csfg*sni2du+snfg*.5*cosi2*di;\n  y5 = sinio2*csfg-snfg*sni2du+csfg*.5*cosi2*di;\n  rr = rm+dr;\n  rdot = deep_arg->xn*am*deep_arg->em*snf/beta+g14*(g2*2.*unmth2*sn2f2g+g4*csfg);\n  r1 = am;\n  rvdot = deep_arg->xn*(r1*r1)*beta/rm+g14*dr+am*g13*deep_arg->sinio*diwc;\n\n  /* Orientation vectors */\n  snlamb = sin(xlamb);\n  cslamb = cos(xlamb);\n  temp = (y5*snlamb-y4*cslamb)*2.;\n  ux = y4*temp+cslamb;\n  vx = y5*temp-snlamb;\n  temp = (y5*cslamb+y4*snlamb)*2.;\n  uy = -y4*temp+snlamb;\n  vy = -y5*temp+cslamb;\n  temp = sqrt(1.-y4*y4-y5*y5)*2.;\n  uz = y4*temp;\n  vz = y5*temp;\n\n  /* Position and velocity */\n  pos[0] = rr*ux*earth_radius_in_km;\n  pos[1] = rr*uy*earth_radius_in_km;\n  pos[2] = rr*uz*earth_radius_in_km;\n  if( vel)\n     {\n     vel[0] = (rdot*ux+rvdot*vx)*earth_radius_in_km;\n     vel[1] = (rdot*uy+rvdot*vy)*earth_radius_in_km;\n     vel[2] = (rdot*uz+rvdot*vz)*earth_radius_in_km;\n     }\n   return( 0);\n} /* SDP8 */\n"
  },
  {
    "path": "sgp.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#define ao       params[0]\n#define qo       params[1]\n#define xlo      params[2]\n#define d1o      params[3]\n#define d2o      params[4]\n#define d3o      params[5]\n#define d4o      params[6]\n#define omgdt    params[7]\n#define xnodot   params[8]\n#define c5       params[9]\n#define c6       params[10]\n\nvoid DLL_FUNC SGP_init( double *params, const tle_t *tle)\n{\n   double c1, c2, c3, c4, r1, cosio, sinio, a1, d1, po, po2no;\n\n   c1 = ck2*1.5;\n   c2 = ck2/4.;\n   c3 = ck2/2.;\n   r1 = ae;\n   c4 = xj3*(r1*(r1*r1))/(ck2*4.);\n   cosio = cos(tle->xincl);\n   sinio = sin(tle->xincl);\n   a1 = pow( xke / tle->xno, two_thirds);\n   d1 = c1/a1/a1*(cosio*3.*cosio-1.)/pow( 1.-tle->eo*tle->eo, 1.5);\n   ao = a1*(1.-d1*.33333333333333331-d1*d1-d1*\n                            1.654320987654321*d1*d1);\n   po = ao*(1.-tle->eo*tle->eo);\n   qo = ao*(1.-tle->eo);\n   xlo = tle->xmo+tle->omegao+tle->xnodeo;\n   d1o = c3*sinio*sinio;\n   d2o = c2*(cosio*7.*cosio-1.);\n   d3o = c1*cosio;\n   d4o = d3o*sinio;\n   po2no = tle->xno/(po*po);\n   omgdt = c1*po2no*(cosio*5.*cosio-1.);\n   xnodot = d3o*-2.*po2no;\n   c5 = c4*.5*sinio*(cosio*5.+3.)/(cosio+1.);\n   c6 = c4*sinio;\n}\n\n\nint DLL_FUNC SGP( const double tsince, const tle_t *tle, const double *params,\n                                     double *pos, double *vel)\n{\n  double\n    temp, rdot, cosu, sinu, cos2u, sin2u, a, e,\n    p, rr, u, ecose, esine, omgas, cosik, xinck,\n    sinik, axnsl, aynsl,\n    sinuk, rvdot, cosuk, coseo1, sineo1, pl,\n    rk, uk, xl, su, ux, uy, uz, vx, vy, vz, pl2,\n    xnodek, cosnok, xnodes, el2, eo1, r1, sinnok,\n    xls, xmx, xmy, tem2, tem5;\n   const double chicken_factor_on_eccentricity = 1.e-6;\n\n   int i, rval = 0;\n\n    /* Update for secular gravity and atmospheric drag */\n   a = tle->xno+(tle->xndt2o*2.+tle->xndd6o*3.*tsince)*tsince;\n   if( a < 0.)\n      rval = SXPX_ERR_NEGATIVE_MAJOR_AXIS;\n   e = e6a;\n   if( e > 1. - chicken_factor_on_eccentricity)\n      rval = SXPX_ERR_NEARLY_PARABOLIC;\n   if( rval)\n      {\n      for( i = 0; i < 3; i++)\n         {\n         pos[i] = 0.;\n         if( vel)\n            vel[i] = 0.;\n         }\n      return( rval);\n      }\n   a = ao * pow( tle->xno / a, two_thirds);\n   if( a * (1. - e) < 1. && a * (1. + e) < 1.)   /* entirely within earth */\n      rval = SXPX_WARN_ORBIT_WITHIN_EARTH;     /* remember, e can be negative */\n   if( a * (1. - e) < 1. || a * (1. + e) < 1.)   /* perigee within earth */\n      rval = SXPX_WARN_PERIGEE_WITHIN_EARTH;\n   if (a > qo) e = 1.-qo/a;\n   p = a*(1.-e*e);\n   xnodes = tle->xnodeo+xnodot*tsince;\n   omgas = tle->omegao+omgdt*tsince;\n   r1 = xlo+(tle->xno+omgdt+xnodot+\n       (tle->xndt2o+tle->xndd6o*tsince)*tsince)*tsince;\n   xls = FMod2p(r1);\n\n   /* Long period periodics */\n   axnsl = e*cos(omgas);\n   aynsl = e*sin(omgas)-c6/p;\n   r1 = xls-c5/p*axnsl;\n   xl = FMod2p(r1);\n\n   /* Solve Kepler's equation */\n   r1 = xl-xnodes;\n   u = FMod2p(r1);\n   eo1 = u;\n   tem5 = 1.;\n\n   i = 0;\n   do\n     {\n       sineo1 = sin(eo1);\n       coseo1 = cos(eo1);\n       if (fabs(tem5) < e6a) break;\n       tem5 = 1.-coseo1*axnsl-sineo1*aynsl;\n       tem5 = (u-aynsl*coseo1+axnsl*sineo1-eo1)/tem5;\n       tem2 = fabs(tem5);\n       if (tem2 > 1.) tem5 = tem2/tem5;\n       eo1 += tem5;\n     }\n   while(i++ < 10);\n\n   /* Short period preliminary quantities */\n   ecose = axnsl*coseo1+aynsl*sineo1;\n   esine = axnsl*sineo1-aynsl*coseo1;\n   el2 = axnsl*axnsl+aynsl*aynsl;\n   pl = a*(1.-el2);\n   pl2 = pl*pl;\n   rr = a*(1.-ecose);\n   rdot = xke*sqrt(a)/rr*esine;\n   rvdot = xke*sqrt(pl)/rr;\n   temp = esine/(sqrt(1.-el2)+1.);\n   sinu = a/rr*(sineo1-aynsl-axnsl*temp);\n   cosu = a/rr*(coseo1-axnsl+aynsl*temp);\n   su = atan2(sinu, cosu);\n\n   /* Update for short periodics */\n   sin2u = (cosu+cosu)*sinu;\n   cos2u = 1.-2.*sinu*sinu;\n   rk = rr+d1o/pl*cos2u;\n   uk = su-d2o/pl2*sin2u;\n   xnodek = xnodes+d3o*sin2u/pl2;\n   xinck = tle->xincl+d4o/pl2*cos2u;\n\n   /* Orientation vectors */\n   sinuk = sin(uk);\n   cosuk = cos(uk);\n   sinnok = sin(xnodek);\n   cosnok = cos(xnodek);\n   sinik = sin(xinck);\n   cosik = cos(xinck);\n   xmx = -sinnok*cosik;\n   xmy = cosnok*cosik;\n   ux = xmx*sinuk+cosnok*cosuk;\n   uy = xmy*sinuk+sinnok*cosuk;\n   uz = sinik*sinuk;\n   vx = xmx*cosuk-cosnok*sinuk;\n   vy = xmy*cosuk-sinnok*sinuk;\n   vz = sinik*cosuk;\n\n   /* Position and velocity */\n   pos[0] = rk*ux*earth_radius_in_km;\n   pos[1] = rk*uy*earth_radius_in_km;\n   pos[2] = rk*uz*earth_radius_in_km;\n   if( vel)\n      {\n      vel[0] = (rdot*ux + rvdot * vx)*earth_radius_in_km;\n      vel[1] = (rdot*uy + rvdot * vy)*earth_radius_in_km;\n      vel[2] = (rdot*uz + rvdot * vz)*earth_radius_in_km;\n      }\n   return( rval);\n} /* SGP */\n"
  },
  {
    "path": "sgp4.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include <stdio.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#define c1           params[2]\n#define c4           params[3]\n#define xnodcf       params[4]\n#define t2cof        params[5]\n#define p_aodp         params[10]\n#define p_cosio        params[11]\n#define p_sinio        params[12]\n#define p_omgdot       params[13]\n#define p_xmdot        params[14]\n#define p_xnodot       params[15]\n#define p_xnodp        params[16]\n#define c5             params[17]\n#define d2             params[18]\n#define d3             params[19]\n#define d4             params[20]\n#define delmo          params[21]\n#define p_eta          params[22]\n#define omgcof         params[23]\n#define sinmo          params[24]\n#define t3cof          params[25]\n#define t4cof          params[26]\n#define t5cof          params[27]\n#define xmcof          params[28]\n#define simple_flag *((int *)( params + 29))\n#define MINIMAL_E    1.e-4\n#define ECC_EPS      1.e-6     /* Too low for computing further drops. */\n\nvoid DLL_FUNC SGP4_init( double *params, const tle_t *tle)\n{\n   deep_arg_t deep_arg;\n   init_t init;\n   double eeta, etasq;\n\n   sxpx_common_init( params, tle, &init, &deep_arg);\n   p_aodp =   deep_arg.aodp;\n   p_cosio =  deep_arg.cosio;\n   p_sinio =  deep_arg.sinio;\n   p_omgdot = deep_arg.omgdot;\n   p_xmdot =  deep_arg.xmdot;\n   p_xnodot = deep_arg.xnodot;\n   p_xnodp =  deep_arg.xnodp;\n   p_eta = deep_arg.aodp*tle->eo*init.tsi;\n// p_eta = init.eta;\n\n   eeta = tle->eo*p_eta;\n   /* For perigee less than 220 kilometers, the \"simple\" flag is set */\n   /* and the equations are truncated to linear variation in sqrt a  */\n   /* and quadratic variation in mean anomaly.  Also, the c3 term,   */\n   /* the delta omega term, and the delta m term are dropped.        */\n   simple_flag = ((p_aodp*(1-tle->eo)/ae) < (220./earth_radius_in_km+ae));\n   if( !simple_flag)\n      {\n      const double c1sq = c1*c1;\n      double temp;\n\n      simple_flag = 0;\n      delmo = 1. + p_eta * cos(tle->xmo);\n      delmo *= delmo * delmo;\n      d2 = 4*p_aodp*init.tsi*c1sq;\n      temp = d2*init.tsi*c1/3;\n      d3 = (17*p_aodp+init.s4)*temp;\n      d4 = 0.5*temp*p_aodp*init.tsi*(221*p_aodp+31*init.s4)*c1;\n      t3cof = d2+2*c1sq;\n      t4cof = 0.25*(3*d3+c1*(12*d2+10*c1sq));\n      t5cof = 0.2*(3*d4+12*c1*d3+6*d2*d2+15*c1sq*(2*d2+c1sq));\n      sinmo = sin(tle->xmo);\n      if( tle->eo < MINIMAL_E)\n         omgcof = xmcof = 0.;\n      else\n         {\n         const double c3 =\n              init.coef * init.tsi * a3ovk2 * p_xnodp * ae * p_sinio / tle->eo;\n\n         xmcof = -two_thirds * init.coef * tle->bstar * ae / eeta;\n         omgcof = tle->bstar*c3*cos(tle->omegao);\n         }\n      } /* End of if (isFlagClear(SIMPLE_FLAG)) */\n   etasq = p_eta * p_eta;\n   c5 = 2*init.coef1*p_aodp * deep_arg.betao2*(1+2.75*(etasq+eeta)+eeta*etasq);\n} /* End of SGP4() initialization */\n\nint DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *params,\n                                                    double *pos, double *vel)\n{\n  double\n        a, e, omega, omgadf,\n        temp, tempa, tempe, templ, tsq,\n        xl, xmdf, xmp, xnoddf, xnode;\n\n  /* Update for secular gravity and atmospheric drag. */\n  xmdf = tle->xmo+p_xmdot*tsince;\n  omgadf = tle->omegao+p_omgdot*tsince;\n  xnoddf = tle->xnodeo+p_xnodot*tsince;\n  omega = omgadf;\n  xmp = xmdf;\n  tsq = tsince*tsince;\n  xnode = xnoddf+xnodcf*tsq;\n  tempa = 1-c1*tsince;\n  tempe = tle->bstar*c4*tsince;\n  templ = t2cof*tsq;\n  if( !simple_flag)\n    {\n      const double delomg = omgcof*tsince;\n      double delm = 1. + p_eta * cos(xmdf);\n      double tcube, tfour;\n\n      delm = xmcof * (delm * delm * delm - delmo);\n      temp = delomg+delm;\n      xmp = xmdf+temp;\n      omega = omgadf-temp;\n      tcube = tsq*tsince;\n      tfour = tsince*tcube;\n      tempa = tempa-d2*tsq-d3*tcube-d4*tfour;\n      tempe = tempe+tle->bstar*c5*(sin(xmp)-sinmo);\n      templ = templ+t3cof*tcube+tfour*(t4cof+tsince*t5cof);\n    }; /* End of if (isFlagClear(SIMPLE_FLAG)) */\n\n  a = p_aodp*tempa*tempa;\n  e = tle->eo-tempe;\n         /* A highly arbitrary lower limit on e,  of 1e-6: */\n  if( e < ECC_EPS)\n     e = ECC_EPS;\n  xl = xmp+omega+xnode+p_xnodp*templ;\n  if( tempa < 0.)       /* force negative a,  to indicate error condition */\n     a = -a;\n  return( sxpx_posn_vel( xnode, a, e, p_cosio, p_sinio, tle->xincl,\n                                          omega, xl, pos, vel));\n} /*SGP4*/\n"
  },
  {
    "path": "sgp8.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include <string.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#define tthmun              params[0]\n#define sini2               params[1]\n#define cosi2               params[2]\n#define unm5th              params[3]\n#define unmth2              params[4]\n#define xmdt1               params[5]\n#define xgdt1               params[6]\n#define xhdt1               params[7]\n#define xndt                params[8]\n#define edot                params[9]\n#define ed                  params[10]\n#define gamma               params[11]\n#define omgdt               params[12]\n#define ovgpp               params[13]\n#define pp                  params[14]\n#define qq                  params[15]\n#define sini                params[16]\n#define cosi                params[17]\n#define cosio_2             params[18]\n#define xlldot              params[19]\n#define xnd                 params[20]\n#define xnodot_             params[21]\n#define xnodp_              params[22]\n#define simple_flag       *((int *)( params + 23))\n\nvoid sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg);\n\nvoid sxp8_common_init( double *params, const tle_t *tle, deep_arg_t *deep_arg)\n{\n   const double half_inclination = tle->xincl*.5;\n   const double cosio4 = deep_arg->cosio2 * deep_arg->cosio2;\n   double po, pom2, pardt1, pardt2, pardt4;\n\n   deep_arg->sing = sin( tle->omegao);\n   deep_arg->cosg = cos( tle->omegao);\n   sini2 = sin( half_inclination);\n   cosi2 = cos( half_inclination);\n   tthmun = deep_arg->cosio2 * 3. - 1.;\n   unm5th = 1.-deep_arg->cosio2 * 5.;\n   unmth2 = 1.-deep_arg->cosio2;\n   po = deep_arg->aodp * deep_arg->betao2;\n   pom2 = 1./(po*po);\n   pardt1 = 3. * ck2 * pom2 * deep_arg->xnodp;\n   pardt2 = pardt1 * ck2 * pom2;\n   pardt4 = ck4 * 1.25 * pom2 * pom2 * deep_arg->xnodp;\n   xmdt1 = .5 * pardt1 * deep_arg->betao * tthmun;\n   xgdt1 = -.5 * pardt1 * unm5th;\n   xhdt1 = -pardt1 * deep_arg->cosio;\n   deep_arg->xmdot = deep_arg->xnodp+xmdt1+pardt2*.0625*deep_arg->betao*\n               (13.-deep_arg->cosio2*78.+cosio4*137.);\n   deep_arg->omgdot = xgdt1+pardt2*.0625*(7.-deep_arg->cosio2*\n                     114.+cosio4*395.)+pardt4*(3.-deep_arg->cosio2*\n                     36.+cosio4*49.);\n   deep_arg->xnodot = xhdt1+(pardt2*.5*(4.-deep_arg->cosio2*19.)+pardt4*\n      2.*(3.-deep_arg->cosio2*7.))*deep_arg->cosio;\n}\n\nvoid DLL_FUNC SGP8_init( double *params, const tle_t *tle)\n{\n   const double rho = .15696615;\n   const double b = tle->bstar*2./rho;\n   double\n         alpha2, b1, b2, b3, c0,\n         c1, c4, c5, cos2g, d1, d2, d3, d4,\n         d5, eeta, eta, eta2, eddot,  etdt,\n         po, psim2, r1, tsi, xndtn;\n   deep_arg_t deep_arg;\n\n   sxpall_common_init( tle, &deep_arg);\n   sxp8_common_init( params, tle, &deep_arg);\n   sini = sin( tle->xincl);\n   cosi = deep_arg.cosio;\n   cosio_2 = deep_arg.cosio2;\n\n   /* Initialization */\n   xnodp_ = deep_arg.xnodp;\n   xlldot = deep_arg.xmdot;\n   omgdt = deep_arg.omgdot;\n   xnodot_ = deep_arg.xnodot;\n   po = deep_arg.aodp * deep_arg.betao2;\n   tsi = 1./(po-s_const);\n   eta = tle->eo*s_const*tsi;\n   eta2 = eta * eta;\n   psim2 = (r1 = 1./(1.-eta2), fabs(r1));\n   alpha2 = deep_arg.eosq+1.;\n   eeta = tle->eo*eta;\n   cos2g = deep_arg.cosg * deep_arg.cosg * 2. - 1.;\n   d5 = tsi*psim2;\n   d1 = d5/po;\n   d2 = eta2*(eta2*4.5+36.)+12.;\n   d3 = eta2*(eta2*2.5+15.);\n   d4 = eta*(eta2*3.75+5.);\n   b1 = ck2*tthmun;\n   b2 = -ck2*unmth2;\n   b3 = a3ovk2*sini;\n   r1 = tsi, r1 *= r1;\n   c0 = b*.5*rho*qoms2t*xnodp_*deep_arg.aodp*(r1*r1)*\n                pow(psim2, 3.5)/sqrt(alpha2);\n   r1 = alpha2;\n   c1 = xnodp_*1.5*(r1*r1)*c0;\n   c4 = d1*d3*b2;\n   c5 = d5*d4*b3;\n   xndt = c1*(eta2*(deep_arg.eosq*34.+3.)+2.+eeta*5.*(eta2+4.)\n     +deep_arg.eosq*8.5+d1*d2*b1+c4*cos2g+c5*deep_arg.sing);\n   xndtn = xndt/xnodp_;\n\n   /* If drag is very small, the isimp flag is set and the */\n   /* equations are truncated to linear variation in mean  */\n   /* motion and quadratic variation in mean anomaly       */\n   r1 = xndtn * minutes_per_day;\n   if( fabs(r1) > .00216)\n      {\n      const double d6 = eta*(eta2*22.5+30.);\n      const double d7 = eta*(eta2*12.5+5.);\n      const double d8 = eta2*(eta2+6.75)+1.;\n      const double d9 = eta*(deep_arg.eosq*68.+6.)+tle->eo*(eta2*15.+20.);\n      const double d10 = eta*5.*(eta2+4.)+tle->eo*(eta2*68.+17.);\n      const double d11 = eta*(eta2*18.+72.);\n      const double d12 = eta*(eta2*10.+30.);\n      const double d13 = eta2*11.25+5.;\n      const double d20 = two_thirds*.5*xndtn;\n      const double c8 = d1*d7*b2;\n      const double c9 = d5*d8*b3;\n      const double sin2g = deep_arg.sing*2.*deep_arg.cosg;\n      double d1dt, d2dt, d3dt, d4dt, d5dt, temp;\n      double d14, d15, d16, d17, d18, d19, d23, d25, aldtal, psdtps;\n      double c4dt, c5dt, c0dtc0, c1dtc1, rr2;\n      double d1ddt, etddt, tmnddt, tsdtts, tsddts, xnddt, xntrdt;\n\n      simple_flag = 0;\n      edot = -c0*(eta*(eta2+4.+deep_arg.eosq*(eta2*7.+15.5))+tle->eo*\n                 (eta2*15.+5.)+d1*d6*b1+c8*cos2g+c9*deep_arg.sing);\n      tsdtts = deep_arg.aodp*2.*tsi*(d20*deep_arg.betao2+tle->eo*edot);\n      aldtal = tle->eo*edot/alpha2;\n      etdt = (edot+tle->eo*tsdtts)*tsi*s_const;\n      psdtps = -eta*etdt*psim2;\n      c0dtc0 = d20+tsdtts*4.-aldtal-psdtps*7.;\n      c1dtc1 = xndtn+aldtal*4.+c0dtc0;\n      d14 = tsdtts-psdtps*2.;\n      d15 = (d20+tle->eo*edot/deep_arg.betao2)*2.;\n      d1dt = d1*(d14+d15);\n      d2dt = etdt*d11;\n      d3dt = etdt*d12;\n      d4dt = etdt*d13;\n      d5dt = d5*d14;\n      c4dt = b2*(d1dt*d3+d1*d3dt);\n      c5dt = b3*(d5dt*d4+d5*d4dt);\n      d16 = d9*etdt+d10*edot+b1*(d1dt*d2+d1*d2dt)+c4dt*\n            cos2g+c5dt*deep_arg.sing+xgdt1*(c5*deep_arg.cosg-c4*2.*sin2g);\n      xnddt = c1dtc1*xndt+c1*d16;\n      eddot = c0dtc0*edot-c0*((eta2*3.+4.+eeta*30.+deep_arg.eosq*\n        (eta2*21.+15.5))*etdt+(eta2*15.+5.+eeta*\n         (eta2*14.+31.))*edot+b1*(d1dt*d6+d1*etdt*\n             (eta2*67.5+30.))+b2*(d1dt*d7+d1*etdt*\n        (eta2*37.5+5.))*cos2g+b3*(d5dt*d8+d5*etdt*eta*\n        (eta2*4.+13.5))*deep_arg.sing+xgdt1*(c9*deep_arg.cosg-c8*2.*sin2g));\n      r1 = edot;\n      d25 = r1*r1;\n      r1 = xndtn;\n      d17 = xnddt/xnodp_-r1*r1;\n      tsddts = tsdtts*2.*(tsdtts-d20)+deep_arg.aodp*tsi*\n               (two_thirds*deep_arg.betao2*d17-d20*4.*tle->eo*edot+\n               (d25+tle->eo*eddot)*2.);\n      etddt = (eddot+edot*2.*tsdtts)*tsi*s_const+tsddts*eta;\n      r1 = tsdtts;\n      d18 = tsddts-r1*r1;\n      r1 = psdtps;\n      rr2 = psdtps;\n      d19 = -(r1*r1)/eta2-eta*etddt*psim2-rr2*rr2;\n      d23 = etdt*etdt;\n      d1ddt = d1dt*(d14+d15)+d1*(d18-d19*2.+two_thirds*d17+\n         (alpha2*d25/deep_arg.betao2+tle->eo*eddot)*2./deep_arg.betao2);\n      r1  = aldtal;\n      xntrdt = xndt*(two_thirds*2.*d17+(d25+tle->eo*eddot)*3./\n               alpha2-r1*r1*6.+d18*4.-d19*7.)+\n                    c1dtc1*xnddt+c1*(c1dtc1*d16+d9*etddt+d10*\n          eddot+d23*(eeta*30.+6.+deep_arg.eosq*68.)+etdt*edot*\n          (eta2*30.+40.+eeta*272.)+d25*(eta2*68.+17.)+\n          b1*(d1ddt*d2+d1dt*2.*d2dt+d1*(etddt*d11+d23*\n          (eta2*54.+72.)))+b2*(d1ddt*d3+d1dt*2.*d3dt+d1\n          *(etddt*d12+d23*(eta2*30.+30.)))*cos2g+b3*\n          ((d5dt*d14+d5*(d18-d19*2.))*d4+d4dt*2.*d5dt+\n          d5*(etddt*d13+eta*22.5*d23))*deep_arg.sing+xgdt1*((d20*\n          7.+tle->eo*4.*edot/deep_arg.betao2)*(c5*deep_arg.cosg-c4*2.*\n          sin2g)+(c5dt*2.*deep_arg.cosg-c4dt*4.*sin2g-xgdt1*\n          (c5*deep_arg.sing+c4*4.*cos2g))));\n      tmnddt = xnddt*1e9;\n      r1 = tmnddt;\n      temp = r1*r1-xndt*1e18*xntrdt;\n      r1 = tmnddt;\n      pp = (temp+r1*r1)/temp;\n      gamma = -xntrdt/(xnddt*(pp-2.));\n      xnd = xndt/(pp*gamma);\n      qq = 1.-eddot/(edot*gamma);\n      ed = edot/(qq*gamma);\n      ovgpp = 1./(gamma*(pp+1.));\n      }\n   else\n      {\n      simple_flag = 1;\n      edot = -two_thirds*xndtn*(1.-tle->eo);\n      }  /* End of if (fabs(r1) > .00216) */\n} /* End of SGP8() initialization */\n\nint DLL_FUNC SGP8( const double tsince, const tle_t *tle, const double *params,\n                                           double *pos, double *vel)\n{\n   int i;\n   double\n        am, aovr, axnm, aynm, beta, beta2m,\n        cose, cosos, cs2f2g, csf, csfg,\n        cslamb, di, diwc, dr, ecosf, em, fm,\n        g1, g10, g13, g14, g2, g3, g4, g5,\n        omgasm, pm, r1, rdot, rm, rr, rvdot,\n        sine, sinos, sn2f2g, snf, snfg,\n        sni2du, snlamb, temp, ux, uy,\n        uz, vx, vy, vz, xlamb, xmam, xn,\n        xnodes, y4, y5, z1, z7, zc2, zc5;\n\n  /* Update for secular gravity and atmospheric drag */\n  r1 = tle->xmo+xlldot*tsince;\n  xmam = FMod2p(r1);\n  omgasm = tle->omegao+omgdt*tsince;\n  xnodes = tle->xnodeo+xnodot_*tsince;\n  if( !simple_flag)\n    {\n      double temp1;\n\n      temp = 1.-gamma*tsince;\n      temp1 = pow(temp, pp);\n      xn = xnodp_+xnd*(1.-temp1);\n      em = tle->eo+ed*(1.-pow(temp, qq));\n      z1 = xnd*(tsince+ovgpp*(temp*temp1-1.));\n    }\n  else\n    {\n      xn = xnodp_+xndt*tsince;\n      em = tle->eo+edot*tsince;\n      z1 = xndt*.5*tsince*tsince;\n    }  /* if(isFlagClear(SIMPLE_FLAG)) */\n\n  z7 = two_thirds*3.5*z1/xnodp_;\n  r1 = xmam+z1+z7*xmdt1;\n  xmam = FMod2p(r1);\n  omgasm += z7*xgdt1;\n  xnodes += z7*xhdt1;\n\n  /* Solve Kepler's equation */\n  zc2 = xmam+em*sin(xmam)*(em*cos(xmam)+1.);\n\n  i = 0;\n  do\n    {\n      double cape;\n\n      sine = sin(zc2);\n      cose = cos(zc2);\n      zc5 = 1./(1.-em*cose);\n      cape = (xmam+em*sine-zc2)*zc5+zc2;\n      r1 = cape-zc2;\n      if(fabs(r1) <= e6a) break;\n      zc2 = cape;\n    }\n  while(i++ < 10 );\n\n  /* Short period preliminary quantities */\n  am = pow( xke / xn, two_thirds);\n  beta2m = 1.-em*em;\n  sinos = sin(omgasm);\n  cosos = cos(omgasm);\n  axnm = em*cosos;\n  aynm = em*sinos;\n  pm = am*beta2m;\n  g1 = 1./pm;\n  g2 = ck2*.5*g1;\n  g3 = g2*g1;\n  beta = sqrt(beta2m);\n  g4 = a3ovk2 * .25 * sini;\n  g5 = a3ovk2 * .25 * g1;\n  snf = beta*sine*zc5;\n  csf = (cose-em)*zc5;\n  fm = atan2(snf, csf);\n  if( fm < 0.)\n     fm += pi + pi;\n  snfg = snf*cosos+csf*sinos;\n  csfg = csf*cosos-snf*sinos;\n  sn2f2g = snfg*2.*csfg;\n  r1 = csfg;\n  cs2f2g = r1*r1*2.-1.;\n  ecosf = em*csf;\n  g10 = fm-xmam+em*snf;\n  rm = pm/(ecosf+1.);\n  aovr = am/rm;\n  g13 = xn*aovr;\n  g14 = -g13*aovr;\n  dr = g2*(unmth2*cs2f2g-tthmun*3.)-g4*snfg;\n  diwc = g3*3.*sini*cs2f2g-g5*aynm;\n  di = diwc*cosi;\n\n  /* Update for short period periodics */\n  sni2du = sini2*(g3*((1.-cosio_2*7.)*.5*sn2f2g-unm5th*3.*g10)-\n      g5*sini*csfg*(ecosf+2.))-g5*.5f*cosio_2*axnm/cosi2;\n  xlamb = fm+omgasm+xnodes+g3*((cosi*6.+1.-cosio_2*7.)*\n     .5*sn2f2g-(unm5th+cosi*2.)*3.*g10)+g5*sini*\n          (cosi*axnm/(cosi+1.)-(ecosf+2.)*csfg);\n  y4 = sini2*snfg+csfg*sni2du+snfg*.5*cosi2*di;\n  y5 = sini2*csfg-snfg*sni2du+csfg*.5*cosi2*di;\n  rr = rm+dr;\n  rdot = xn*am*em*snf/beta+g14*(g2*2.*unmth2*sn2f2g+g4*csfg);\n  r1 = am;\n  rvdot = xn*(r1*r1)*beta/rm+g14 *\n          dr+am*g13*sini*diwc;\n\n  /* Orientation vectors */\n  snlamb = sin(xlamb);\n  cslamb = cos(xlamb);\n  temp = (y5*snlamb-y4*cslamb)*2.;\n  ux = y4*temp+cslamb;\n  vx = y5*temp-snlamb;\n  temp = (y5*cslamb+y4*snlamb)*2.;\n  uy = -y4*temp+snlamb;\n  vy = -y5*temp+cslamb;\n  temp = sqrt(1.-y4*y4-y5*y5)*2.;\n  uz = y4*temp;\n  vz = y5*temp;\n\n  /* Position and velocity */\n  pos[0] = rr*ux*earth_radius_in_km;\n  pos[1] = rr*uy*earth_radius_in_km;\n  pos[2] = rr*uz*earth_radius_in_km;\n  if( vel)\n     {\n     vel[0] = (rdot*ux+rvdot*vx)*earth_radius_in_km;\n     vel[1] = (rdot*uy+rvdot*vy)*earth_radius_in_km;\n     vel[2] = (rdot*uz+rvdot*vz)*earth_radius_in_km;\n     }\n   return( 0);\n} /* SGP8 */\n"
  },
  {
    "path": "sm_sat.def",
    "content": "LIBRARY sm_sat\r\nEXPORTS\r\n   SGP4_init                    @2\r\n   SGP4                         @7\r\n   select_ephemeris             @11\r\n   parse_elements               @12\r\n   tle_checksum                 @17\r\n\r\n"
  },
  {
    "path": "ssc_eph.c",
    "content": "/* Code to convert ephems from SSCWeb into the format 'eph2tle'\nuses to generate TLEs.  The usefulness here is that you can\nget TLEs for the Magnetospheric Multiscale (MMS) satellites that\nare good enough to ID the individual satellites,  even though\nthe four of them are in a tight cluster.  This does fail if the\nsatellites maneuver during the course of a day;  the TLEs are\nthen unable to fit the (non-gravitational) motion.  Compile with\n\ngcc -I../include -Wextra -Wall -O3 -pedantic ssc_eph.c -o ssc_eph ../lib/liblunar.a -lm\n\n  Go to the SSCWeb Locator page :\n\nhttps://sscweb.gsfc.nasa.gov/cgi-bin/Locator.cgi\n\n   Select the 'Standard' interface.  Turn on MMS-1, 2, 3,  and 4.\nSet the output frequency to 144 minutes (= 0.1 day).  Under\n'Optional Settings',  select kilometers and yy/mm/dd output.\nSelect the desired time span.\n\n   Under 'Output Options',  select GEI/J2000 XYZ.\n\n   Generate the output and save it as /tmp/mms.txt.  The result\ncan be fed through 'eph2tle' (see the 'find_orb' repository) to\ngenerate TLEs.       */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n#include \"watdefs.h\"\n#include \"date.h\"\n\n#define is_power_of_two( X)   (!((X) & ((X) - 1)))\n\nstatic void dump_to_file( const int mms_no, const double t0, const double dt,\n                     const int n_found, const double *xyz)\n{\n   FILE *ofile;\n   char buff[200];\n   int i, j;\n\n   snprintf( buff, sizeof( buff), \"mms%d.txt\", mms_no);\n   ofile = fopen( buff, \"wb\");\n   assert( ofile);\n   fprintf( ofile, \"%f %f %d 0,149597870.700000,86400.000000,2000\"\n                     \" (500) Geocentric: MMS-%d = 2015-011%c = NORAD %d\\n\",\n                            t0, dt, n_found, mms_no, mms_no + '@', mms_no + 40481);\n   for( i = 0; i < n_found; i++)\n      {\n      double vel[3];\n      const double *tptr = xyz + i * 3;\n\n      for( j = 0; j < 3; j++)\n         vel[j] = (i == n_found - 1 ? tptr : tptr + 3)[j];\n      for( j = 0; j < 3; j++)\n         {\n         vel[j] -= (i ? tptr - 3 : tptr)[j];\n         vel[j] /= 86400. * dt;\n         if( i && i != n_found - 1)\n            vel[j] /= 2.;\n         }\n      fprintf( ofile, \"%f %11.2f %11.2f %11.2f %11.6f %11.6f %11.6f\\n\",\n                  t0 + (double)i * dt,\n                  tptr[0], tptr[1], tptr[2], vel[0], vel[1], vel[2]);\n      }\n   fclose( ofile);\n}\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile = fopen( \"/tmp/mms.txt\", \"rb\");\n   char buff[100];\n   long header_offset = 0;\n   int mms_no;\n\n   assert( ifile);\n   while( !header_offset && fgets( buff, sizeof( buff), ifile))\n      if( !memcmp( buff, \"yy/mm/dd\", 8))\n         header_offset = ftell( ifile);\n   if( !header_offset)\n      {\n      fprintf( stderr, \"Couldn't find yy/mm/dd header\\n\");\n      return( -1);\n      }\n   for( mms_no = 1; mms_no <= 4; mms_no++)\n      {\n      int n_found = 0;\n      double t0 = 0., step = 0., *xyz = NULL;\n      const double td_minus_utc = 69.184 / 86400.;\n      char *tptr;\n\n      fseek( ifile, header_offset, SEEK_SET);\n      buff[0] = '2';       /* assume 2nd millennium */\n      buff[1] = '0';       /* assume 21st century */\n      while( fgets( buff + 2, sizeof( buff) - 2, ifile))\n         if( (tptr = strstr( buff, \"mms\")) != NULL && tptr[3] == '0' + mms_no)\n            {\n            int n_fields_read;\n\n            tptr[-1] = '\\0';\n            if( !n_found)\n               t0 = get_time_from_string( 0., buff, FULL_CTIME_YMD, NULL);\n            if( n_found == 1)\n               step = get_time_from_string( 0., buff, FULL_CTIME_YMD, NULL) - t0;\n            n_found++;\n            if( is_power_of_two( n_found))\n               xyz = (double *)realloc( xyz, 2 * n_found * 3 * sizeof( double));\n            n_fields_read = sscanf( tptr + 4, \"%lf %lf %lf\",\n                        xyz + n_found * 3 - 3,\n                        xyz + n_found * 3 - 2,\n                        xyz + n_found * 3 - 1);\n            assert( 3 == n_fields_read);\n            }\n      dump_to_file( mms_no, t0 + td_minus_utc, step, n_found, xyz);\n      free( xyz);\n      }\n   fclose( ifile);\n   return( 0);\n}\n"
  },
  {
    "path": "summarize.c",
    "content": "/* Code to read 'tle_list.txt' and insert # Range: lines for\nfiles that lack them,  and (for files containing only one\nobject) NORAD/COSPAR identifiers.   The former can be used to\nspeed up some programs (by skipping 'included' files that\nwon't cover a desired time span).  The latter can be used in\nan ephemeris program to quickly find a desired object.\n\nImportant notes :\n\n   -- tle_list.txt will require occasional updating,  of course,\nas new objects are added and archival Space-Track and other TLE\nsets are added.  My hope is to run this once and then add\n# Range: and # ID: lines as needed.\n\n   -- THEMIS-A, D, and E are oddball cases.  They maneuver;  I\nget state vectors from UC Berkeley and compute TLEs from those\n(see 'up_them' in the 'tles' repository).  Those are updated\nroughly weekly.  I don't want to have to update 'tle_list' each\ntime for that,  so I've pushed the day of reckoning for those\noff by a year.  */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#include \"watdefs.h\"\n#include \"afuncs.h\"\n#include \"date.h\"\n#include \"norad.h\"\n\nstatic void get_range_info( const char *filename,\n               const int range_already_set)\n{\n   char buff[200], *tptr, id[100];\n   int n_ids_found = 0;\n   const int is_themis = !memcmp( filename, \"07004\", 5)\n                           && filename[5] < 'g';\n   FILE *ifile;\n\n   strcpy( buff, filename);\n   tptr = strchr( buff, '\\n');\n   assert( tptr);\n   *tptr = '\\0';\n   ifile = fopen( buff, \"rb\");\n   assert( ifile);\n   *id = '\\0';\n   while( 2 > n_ids_found && fgets( buff, sizeof( buff), ifile))\n      {\n      if( !memcmp( buff, \"# Ephem range:\", 14) && !range_already_set)\n         {\n         double mjd[2];\n         size_t i;\n         const int n_read = sscanf( buff + 14, \"%lf %lf\", mjd, mjd + 1);\n\n         assert( n_read == 2);\n         if( is_themis)          /* THEMIS sats get updated/extended */\n            mjd[1] += 365.;  /* regularly;  'pad' the end date accordingly */\n         printf( \"# Range:\");\n         for( i = 0; i < 2; i++)\n            {\n            full_ctime( buff, 2400000.5 + mjd[i],\n                  FULL_CTIME_YMD | FULL_CTIME_DATE_ONLY\n                  | FULL_CTIME_LEADING_ZEROES | FULL_CTIME_MONTHS_AS_DIGITS);\n            buff[4] = buff[7] = '-';\n            printf( \" %s\", buff);\n            }\n         printf( \"\\n\");\n         }\n      else if( *buff == '1' && !tle_checksum( buff))\n         {\n         buff[7] = ' ';\n         buff[17] = '\\0';\n         if( strcmp( id, buff + 2))\n            n_ids_found++;\n         strcpy( id, buff + 2);\n         }\n      }\n   if( n_ids_found == 1)\n      printf( \"# ID: %s\\n\", id);\n   fclose( ifile);\n}\n\nint main( const int unused_argc, const char **unused_argv)\n{\n   char prev_line[100], line[100];\n   FILE *ifile = fopen( \"tle_list.txt\", \"rb\");\n\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argc);\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argv);\n   assert( ifile);\n   *prev_line = '\\0';\n   while( fgets( line, sizeof( line), ifile))\n      {\n      if( !memcmp( line, \"# Include \", 10)\n                     && memcmp( line + 10, \"old_tles\", 8))\n         get_range_info( line + 10, !memcmp( prev_line, \"# Range:\", 8));\n      printf( \"%s\", line);\n      strcpy( prev_line, line);\n      }\n   fclose( ifile);\n   return( 0);\n}\n"
  },
  {
    "path": "test.tle",
    "content": "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\r\n\r\n1 11801U          80230.29629788  .01431103  00000-0  14311-1       2\r\n2 11801U 46.7916 230.4354 7318036  47.4722  10.4117  2.28537848     2\r\n\r\nTimes: 0 360 720 1080 1440\r\n\r\n# I played around with some zero-e satellites to check that bug:\r\n#1 11801U          80230.29629788  .01431103  00000-0  14311-1       2\r\n#2 11801U 46.7916 230.4354 0000000  47.4722  10.4117  2.28537848     4\r\n\r\n1 23581U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\r\n2 23581   1.1236  93.7945 0005741 214.4722 151.5103  1.00270260 23672\r\n   /* above is GOES 9 */\r\n1 11871U 80057A   01309.36911127 -.00000499 +00000-0 +10000-3 0 08380\r\n2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886\r\n   /* above is Cosmos 1191 */\r\n1 09931U 77029A   01309.17453186 -.00000329 +00000-0 +10000-3 0 05967\r\n2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451\r\n\r\n         /* Switch around to different times to test: */\r\n\r\nTimes: 1440 0 360 1080 720 719 -2880\r\n   /* Cosmos 1217 */\r\n1 12032U 80085A   01309.42683181  .00000182  00000-0  10000-3 0  3499\r\n2 12032  65.2329  86.7607 7086222 172.0967 212.4632  2.00879501101699\r\n   /* Molniya 3-19Rk */\r\n1 13446U 82083E   01283.10818257  .00098407  45745-7  54864-3 0  6240\r\n2 13446  62.1717  83.8458 7498877 273.9677 320.2568  2.06357523137203\r\n   /* Ariane Deb */\r\n1 23246U 91015G   01311.70347086  .00004957  00000-0  43218-2 0  8190\r\n2 23246   7.1648 263.6949 5661268 241.8299  50.5793  4.44333001129208\r\n1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    87\r\n2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  1058\r\n\r\n1 00553U 63004A   77069.11186343 -.00000050 +00000-0 +00000-0 0 00056\r\n2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106\r\n   /* above is Syncom 1 */\r\nUnknown 98003\r\n1 98003U          01079.82102009 0.00000000  00000-0 +00000+0 0    01\r\n2 98003  15.5935 354.8013 0015600 263.1079  96.8986  0.99816000    03\r\n95586 A\r\n1 99331U 95586 A  01083.00000000  .00000000  00000-0  00000+0 0    02\r\n2 99331   2.0413  66.6424 0042740 166.0448 281.2000  1.00273142    04\r\n96750A\r\n1 99337U 96750A   01088.00000000  .00000000  00000-0  00000+0 0    07\r\n2 99337   6.1829 349.1711 0918991 187.9440 347.1192  1.00269427    03\r\nIridium 921tum                  5.5    6.6\r\n1 24873U 97034E   02082.49700151  .00007707  00000-0  80741-3 0  6717\r\n2 24873  86.3901 131.0630 0010160 338.7719  21.3095 14.89420428254310\r\n96051J\r\n1 24875U 96051J   02082.46969260  .00045815  00000-0  12983-1 0   540\r\n2 24875  71.1295 245.1704 0037175  68.5321 292.0237 14.44819470284117\r\n97035A\r\n1 24876U 97035A   02078.28561105 +.00000010 +00000-0 +00000-0 0 09256\r\n2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149\r\n94029GM\r\n1 24138U 94029GM  02078.77267326 +.00026592 +00000-0 +63509-2 0 09707\r\n2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361\r\n94029HA\r\n1 24151U 94029HA  02078.68402149 +.00005369 +00000-0 +11280-2 0 09568\r\n2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215\r\n94029HF\r\n1 24156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\r\n2 24156  82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579\r\nMolniya 2-9                     4.5     15\r\n1 07276U 74026A   02076.77143097 -.00000040 +00000-0 +10000-3 0 09926\r\n2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456\r\n73086EZ\r\n1 07191U 73086EZ  02076.63595131 -.00000031  00000-0  10000-3 0  9045\r\n2 07191 102.1490 334.0121 0253607  54.7437 307.7251 12.10431881825991\r\n73086FA\r\n1 07192U 73086FA  02077.02656547 -.00000031  00000-0  10000-3 0  6091\r\n2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610\r\nGPS-0003                        5.0    .53\r\n1 11054U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\r\n2 11054  62.4872 198.8383 0057450 192.9653 166.8284  1.93504662169140\r\nMolniya 3-10     5.0  0.0  0.0  4.5     15\r\n1 11057U 78095A   02077.63138055  .00000149  00000-0  10000-3 0  5309\r\n2 11057  63.7422 203.7241 6717022 282.5815  14.8175  2.34176921180342\r\n99066A\r\n1 25989U 99066A   02080.37500000  .00000000  00000-0  00000+0 0  4082\r\n2 25989  34.6613 172.6861 7995138 107.4591   3.7150  0.50103982  1159\r\nXMM Ari Rk                      3.0     20\r\n1 25990U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\r\n2 25990  44.2277 151.3168 8216838 138.1402 356.4324  0.55308221  1445\r\n\r\n      Now do some tests of \"plain,  ordinary\" SGP:\r\n\r\nEphem 1\r\n\r\n1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    87\r\n2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  1058\r\n73086EZ\r\n1 07191U 73086EZ  02076.63595131 -.00000031  00000-0  10000-3 0  9045\r\n2 07191 102.1490 334.0121 0253607  54.7437 307.7251 12.10431881825991\r\n94029HA\r\n1 24151U 94029HA  02078.68402149 +.00005369 +00000-0 +11280-2 0 09568\r\n2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215\r\n\r\n      Now test some SxP8 cases:\r\n\r\nEphem 4\r\n\r\n1 11801U          80230.29629788  .01431103  00000-0  14311-1       2\r\n2 11801U 46.7916 230.4354 7318036  47.4722  10.4117  2.28537848     2\r\nTimes: 1440 0 360 1080 720 719 -2880\r\n1 23581U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\r\n2 23581   1.1236  93.7945 0005741 214.4722 151.5103  1.00270260 23672\r\n   /* above is GOES 9 */\r\n1 11871U 80057A   01309.36911127 -.00000499 +00000-0 +10000-3 0 08380\r\n2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886\r\n   /* above is Cosmos 1191 */\r\n1 09931U 77029A   01309.17453186 -.00000329 +00000-0 +10000-3 0 05967\r\n2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451\r\n   /* Cosmos 1217 */\r\n1 12032U 80085A   01309.42683181  .00000182  00000-0  10000-3 0  3499\r\n2 12032  65.2329  86.7607 7086222 172.0967 212.4632  2.00879501101699\r\n   /* Molniya 3-19Rk */\r\n1 13446U 82083E   01283.10818257  .00098407  45745-7  54864-3 0  6240\r\n2 13446  62.1717  83.8458 7498877 273.9677 320.2568  2.06357523137203\r\n   /* Ariane Deb */\r\n1 23246U 91015G   01311.70347086  .00004957  00000-0  43218-2 0  8190\r\n2 23246   7.1648 263.6949 5661268 241.8299  50.5793  4.44333001129208\r\n1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    87\r\n2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  1058\r\n\r\n1 00553U 63004A   77069.11186343 -.00000050 +00000-0 +00000-0 0 00056\r\n2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106\r\n   /* above is Syncom 1 */\r\n\r\n      Now switch back to SxP4:\r\nEphem 2\r\nUnknown 98003\r\n1 98003U          01079.82102009 0.00000000  00000-0 +00000+0 0    01\r\n2 98003  15.5935 354.8013 0015600 263.1079  96.8986  0.99816000    03\r\n95586 A\r\n1 99331U 95586 A  01083.00000000  .00000000  00000-0  00000+0 0    02\r\n2 99331   2.0413  66.6424 0042740 166.0448 281.2000  1.00273142    04\r\n96750A\r\n1 99337U 96750A   01088.00000000  .00000000  00000-0  00000+0 0    07\r\n2 99337   6.1829 349.1711 0918991 187.9440 347.1192  1.00269427    03\r\nIridium 921tum                  5.5    6.6\r\n1 24873U 97034E   02082.49700151  .00007707  00000-0  80741-3 0  6717\r\n2 24873  86.3901 131.0630 0010160 338.7719  21.3095 14.89420428254310\r\n96051J\r\n1 24875U 96051J   02082.46969260  .00045815  00000-0  12983-1 0   540\r\n2 24875  71.1295 245.1704 0037175  68.5321 292.0237 14.44819470284117\r\n97035A\r\n1 24876U 97035A   02078.28561105 +.00000010 +00000-0 +00000-0 0 09256\r\n2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149\r\n94029GM\r\n1 24138U 94029GM  02078.77267326 +.00026592 +00000-0 +63509-2 0 09707\r\n2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361\r\n94029HA\r\n1 24151U 94029HA  02078.68402149 +.00005369 +00000-0 +11280-2 0 09568\r\n2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215\r\n94029HF\r\n1 24156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\r\n2 24156  82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579\r\nMolniya 2-9                     4.5     15\r\n1 07276U 74026A   02076.77143097 -.00000040 +00000-0 +10000-3 0 09926\r\n2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456\r\n73086EZ\r\n1 07191U 73086EZ  02076.63595131 -.00000031  00000-0  10000-3 0  9045\r\n2 07191 102.1490 334.0121 0253607  54.7437 307.7251 12.10431881825991\r\n73086FA\r\n1 07192U 73086FA  02077.02656547 -.00000031  00000-0  10000-3 0  6091\r\n2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610\r\nGPS-0003                        5.0    .53\r\n1 11054U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\r\n2 11054  62.4872 198.8383 0057450 192.9653 166.8284  1.93504662169140\r\nMolniya 3-10     5.0  0.0  0.0  4.5     15\r\n1 11057U 78095A   02077.63138055  .00000149  00000-0  10000-3 0  5309\r\n2 11057  63.7422 203.7241 6717022 282.5815  14.8175  2.34176921180342\r\n99066A\r\n1 25989U 99066A   02080.37500000  .00000000  00000-0  00000+0 0  4082\r\n2 25989  34.6613 172.6861 7995138 107.4591   3.7150  0.50103982  1159\r\nXMM Ari Rk                      3.0     20\r\n1 25990U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\r\n2 25990  44.2277 151.3168 8216838 138.1402 356.4324  0.55308221  1445\r\n           28 Jun 2007: test some imaginary 0-incl and 90-incl cases\r\n           (Just taking some above cases and resetting incl)\r\nXMM Ari Rk                      3.0     20\r\n1 95990U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\r\n2 95990  00.0000 151.3168 8216838 138.1402 356.4324  0.55308221  1445\r\nXMM Ari Rk                      3.0     20\r\n1 95991U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\r\n2 95991  90.0000 151.3168 8216838 138.1402 356.4324  0.55308221  1445\r\nGPS-0003                        5.0    .53\r\n1 91054U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\r\n2 91054  00.0000 198.8383 0057450 192.9653 166.8284  1.93504662169140\r\nGPS-0003                        5.0    .53\r\n1 91055U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\r\n2 91055  90.0000 198.8383 0057450 192.9653 166.8284  1.93504662169140\r\nGOES-9\r\n1 93581U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\r\n2 93581   0.0000  93.7945 0005741 214.4722 151.5103  1.00270260 23672\r\nGOES-9\r\n1 93582U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\r\n2 93582  90.0000  93.7945 0005741 214.4722 151.5103  1.00270260 23672\r\n94029HF\r\n1 94156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\r\n2 94156  00.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579\r\n94029HF\r\n1 94156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\r\n2 94156  90.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579\r\n"
  },
  {
    "path": "test2.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  test2.cpp      5 July 2002\n *\n *  A skeleton main() function to demonstrate the use of\n *  the various NORAD ephemerides.  It reads in the file\n *  'test.tle' and computes ephemerides at assorted times for\n *  all elements in the file.  The times used,  and the choice\n *  of SxP4 vs. SxP8,  can be switched with various keywords in\n *  the 'test.tle' file.  The result should match 'test2.txt'.\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"norad.h\"\n\n/* Main program */\nint main( int argc, char **argv)\n{\n   double vel[3], pos[3]; /* Satellite position and velocity vectors */\n   FILE *ifile = fopen( (argc == 1) ? \"test.tle\" : argv[1], \"rb\");\n   tle_t tle; /* Pointer to two-line elements set for satellite */\n   char line1[100], line2[100];\n   int n_times = 5, dundee_output = 0;\n   double times[400];\n   int ephem = TLE_EPHEMERIS_TYPE_SGP4;       /* default to SGP4 */\n   int i;               /* Index for loops etc */\n\n   for( i = 2; i < argc; i++)\n      switch( argv[i][0])\n         {\n         case 's': case 'S':\n            sxpx_set_dpsec_integration_step( atof( argv[i] + 1));\n            break;\n         case 'd': case 'D':\n            sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1);\n            break;\n         case 'o': case 'O':\n            {\n            const int new_order = atoi( argv[i] + 1);\n\n            sxpx_set_implementation_param(\n                               SXPX_DPSEC_INTEGRATION_ORDER, new_order);\n            printf( \"Set to order %d\\n\", new_order);\n            }\n            break;\n         default:\n            break;\n         }\n\n   if( !ifile)\n      {\n      printf( \"Couldn't open input TLE file\\n\");\n      exit( -1);\n      }\n   for( i = 0; i < n_times; i++)\n      times[i] = (double)(i * 30);\n   if( fgets( line1, sizeof( line1), ifile))\n      while( fgets( line2, sizeof( line2), ifile))\n         {\n         if( !strncmp( line2, \"Ephem \", 6))\n            ephem = (line2[6] - '0');\n         else if( !strncmp( line2, \"Dundee \", 7))\n            {\n            double t0, step;\n\n            sscanf( line2 + 7, \"%lf %lf %d\", &t0, &step, &n_times);\n            for( i = 0; i < n_times; i++)\n               times[i] = t0 + (double)i * step;\n            sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1);\n            dundee_output = 1;\n            }\n         else if( !strncmp( line2, \"Times: \", 7))\n            {\n            int loc = 7, bytes_read;\n\n            n_times = 0;\n            while( sscanf( line2 + loc, \"%lf%n\", times + n_times, &bytes_read) == 1)\n               {\n               loc += bytes_read;\n               n_times++;\n               }\n            }\n         else if( parse_elements( line1, line2, &tle) >= 0)\n            {                              /* hey! we got a TLE! */\n            int is_deep = select_ephemeris( &tle);\n            const char *ephem_names[6] = { \"???\", \"SGP \", \"SGP4\", \"SDP4\", \"SGP8\", \"SDP8\" };\n            double sat_params[N_SAT_PARAMS];\n\n            if( is_deep)\n               if( ephem == TLE_EPHEMERIS_TYPE_SGP4 || ephem == TLE_EPHEMERIS_TYPE_SGP8)\n                  ephem++;    /* switch to an SDPx model */\n            if( !is_deep)\n               if( ephem == TLE_EPHEMERIS_TYPE_SDP4 || ephem == TLE_EPHEMERIS_TYPE_SDP8)\n                  ephem--;    /* switch to an SGPx model */\n            line1[69] = line2[69] = '\\0';\n            if( dundee_output)\n               printf( \"#%s\\n#%s\\n\", line1, line2);\n            else\n               printf( \"%s\\n%s\\n\", line1, line2);\n            if( is_deep)\n               printf(\"Deep-Space type Ephemeris (%s) selected:\",\n                                          ephem_names[ephem]);\n            else\n               printf(\"Near-Earth type Ephemeris (%s) selected:\",\n                                          ephem_names[ephem]);\n\n            /* Print some titles for the results */\n            printf(\"\\nEphem:%s   Tsince         \"\n                \"X/Xdot           Y/Ydot           Z/Zdot\\n\", ephem_names[ephem]);\n\n            /* Calling of NORAD routines */\n            /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8)   */\n            /* will be called in turn with the appropriate TLE set */\n            switch( ephem)\n               {\n               case TLE_EPHEMERIS_TYPE_SGP:\n                  SGP_init( sat_params, &tle);\n                  break;\n               case TLE_EPHEMERIS_TYPE_SGP4:\n                  SGP4_init( sat_params, &tle);\n                  break;\n               case TLE_EPHEMERIS_TYPE_SGP8:\n                  SGP8_init( sat_params, &tle);\n                  break;\n               case TLE_EPHEMERIS_TYPE_SDP4:\n                  SDP4_init( sat_params, &tle);\n                  break;\n               case TLE_EPHEMERIS_TYPE_SDP8:\n                  SDP8_init( sat_params, &tle);\n                  break;\n               }\n\n            for( i = 0; i < n_times; i++)\n               {\n               switch( ephem)\n                  {\n                  case TLE_EPHEMERIS_TYPE_SGP:\n                     SGP(times[i], &tle, sat_params, pos, vel);\n                     break;\n                  case TLE_EPHEMERIS_TYPE_SGP4:\n                     SGP4(times[i], &tle, sat_params, pos, vel);\n                     break;\n                  case TLE_EPHEMERIS_TYPE_SGP8:\n                     SGP8(times[i], &tle, sat_params, pos, vel);\n                     break;\n                  case TLE_EPHEMERIS_TYPE_SDP4:\n                     SDP4(times[i], &tle, sat_params, pos, vel);\n                     break;\n                  case TLE_EPHEMERIS_TYPE_SDP8:\n                     SDP8(times[i], &tle, sat_params, pos, vel);\n                     break;\n                  }\n\n               /* Calculate and print results */\n               vel[0] /= 60.;    /* cvt km/minute to km/second */\n               vel[1] /= 60.;\n               vel[2] /= 60.;\n\n               if( dundee_output)\n                  {\n                  printf(\"%12.4f   %16.8f %16.8f %16.8f\",\n                                  times[i],pos[0],pos[1],pos[2]);\n                  printf(\" %16.8f %16.8f %16.8f\\n\",\n                                           vel[0],vel[1],vel[2]);\n                  }\n               else\n                  {\n                  printf(\"%5d  %12.4f   %16.8f %16.8f %16.8f\\n\", tle.norad_number,\n                                  times[i],pos[0],pos[1],pos[2]);\n                  printf(\"                      %16.8f %16.8f %16.8f\\n\",\n                                         vel[0],vel[1],vel[2]);\n                  }\n               } /* End of for( i = 0; i < n_times; i++)  */\n            printf( \"\\n\");\n            }\n         strcpy( line1, line2);\n         }\n  fclose( ifile);\n  return(0);\n} /* End of main() */\n\n/*------------------------------------------------------------------*/\n"
  },
  {
    "path": "test2.txt",
    "content": "1 11801U          80230.29629788  .01431103  00000-0  14311-1       2\n2 11801U 46.7916 230.4354 7318036  47.4722  10.4117  2.28537848     2\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11801        0.0000      7473.37102491     428.94748312    5828.74846783\n                            5.10715539       6.44468030      -0.18613330\n11801       30.0000     12817.23227575   10727.64071596    3224.73035812\n                            1.55348611       4.88681176      -2.04097954\n11801       60.0000     14347.23583626   18291.73191417    -650.88975796\n                            0.31695966       3.60827212      -2.18699629\n11801       90.0000     14313.54593833   23940.73287015   -4510.03744310\n                           -0.29382550       2.72035225      -2.08527406\n11801      120.0000     13433.32206997   28200.97792854   -8121.65089654\n                           -0.65569830       2.04581505      -1.92429145\n\n1 23581U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\n2 23581   1.1236  93.7945 0005741 214.4722 151.5103  1.00270260 23672\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n23581        0.0000     -7160.41365734   41573.61482091      85.47880804\n                           -3.02812768      -0.52082979       0.05893406\n23581      360.0000    -41536.21736236   -7273.04931852     809.12349307\n                            0.53157545      -3.02755929      -0.00641841\n23581      720.0000      7464.73494206  -41476.91139123     -91.39056841\n                            3.02688546       0.54571783      -0.05902754\n23581     1080.0000     41434.84155007    7708.64310940    -808.83820227\n                           -0.56086085       3.02392840       0.00697885\n23581     1440.0000     -7871.57126245   41445.00919691      99.12160293\n                           -3.01873852      -0.57266839       0.05898119\n\n1 11871U 80057A   01309.36911127 -.00000499 +00000-0 +10000-3 0 08380\n2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11871        0.0000     43282.52935746    1440.14268930      -0.01880874\n                            0.08916078       0.70325544       1.69640498\n11871      360.0000     -8886.88881104    1787.65122655    5038.32829715\n                           -2.42927268      -2.92232292      -6.88492344\n11871      720.0000     43297.72522868    1558.05868418     352.88012824\n                            0.04464192       0.70168665       1.69622177\n11871     1080.0000     -9328.72863362    1155.93861586    3528.92688833\n                           -1.69877545      -3.03902445      -7.22864706\n11871     1440.0000     43303.79129284    1675.62317479     705.61253596\n                            0.00018291       0.70000195       1.69566320\n\n1 09931U 77029A   01309.17453186 -.00000329 +00000-0 +10000-3 0 05967\n2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 9931        0.0000     -1450.46578294  -13892.29635713       0.21811478\n                            5.30608015       2.82110366       2.48435091\n 9931      360.0000    -37380.50682907   14075.66229597  -19257.82108262\n                           -0.81609862      -1.55557599      -0.32333790\n 9931      720.0000     -5779.43293287  -15647.33186164   -2041.47031557\n                            4.98318767       1.44460184       2.39381161\n 9931     1080.0000    -36647.73763359   15310.44505813  -18967.56466829\n                           -0.95882958      -1.49958741      -0.39571389\n 9931     1440.0000     -9777.78151614  -16467.82307878   -3965.44782190\n                            4.55173861       0.54513994       2.22628227\n\n1 12032U 80085A   01309.42683181  .00000182  00000-0  10000-3 0  3499\n2 12032  65.2329  86.7607 7086222 172.0967 212.4632  2.00879501101699\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n12032     1440.0000      2331.62892366   44022.20816050     554.21122658\n                           -0.72034020      -0.57755776       1.48580161\n12032        0.0000      2510.47723752   44233.19407940       0.93706893\n                           -0.71498431      -0.50248390       1.48587218\n12032      360.0000      8450.60804136    7596.98538745  -17343.84383269\n                            0.97944154       4.47406400      -1.56440368\n12032     1080.0000      8636.82456996    8420.67864547  -17626.79631967\n                            0.91532271       4.40846479      -1.42419046\n12032      720.0000      2421.36024093   44131.25141321     277.59524786\n                           -0.71766025      -0.53991700       1.48595676\n12032      719.0000      2464.34935316   44163.28337493     188.44421966\n                           -0.71698368      -0.52770165       1.48602103\n12032    -2880.0000      2860.46995216   44570.57466099   -1104.55343567\n                           -0.70434023      -0.35454669       1.48326012\n\n1 13446U 82083E   01283.10818257  .00098407  45745-7  54864-3 0  6240\n2 13446  62.1717  83.8458 7498877 273.9677 320.2568  2.06357523137203\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n13446     1440.0000     -2156.94601599  -12986.70815558    1319.55403922\n                            2.99651462       3.43770618      -4.91521599\n13446        0.0000     -8858.01184499  -17331.75404577   13120.92920652\n                            2.05423032       0.46265679      -3.77255550\n13446      360.0000    -20305.89938689    4925.35315665   39231.84704259\n                           -0.47149899      -1.43712333       0.59080129\n13446     1080.0000    -20848.14292436    2991.39610461   39855.37807885\n                           -0.35039272      -1.46076889       0.35347694\n13446      720.0000     -5855.20063554  -16115.69300375    7668.38932692\n                            2.47282838       1.42587377      -4.35844448\n13446      719.0000     -6002.95015689  -16199.52165415    7929.07184135\n                            2.45181263       1.36859026      -4.33078580\n13446    -2880.0000    -16562.18261910  -15455.95800455   28206.33120550\n                            1.07001582      -0.90409223      -2.19013296\n\n1 23246U 91015G   01311.70347086  .00004957  00000-0  43218-2 0  8190\n2 23246   7.1648 263.6949 5661268 241.8299  50.5793  4.44333001129208\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n23246     1440.0000     21837.19258152   -9442.43552275    2873.31676381\n                            0.50387639       2.74057691       0.01948022\n23246        0.0000     -1584.31681305  -14414.24207936       0.71409546\n                            4.09367813      -3.52120490       0.56056050\n23246      360.0000      7115.97219959  -18536.64843313    1154.11312419\n                            3.73304414      -0.64667684       0.47582954\n23246     1080.0000     19317.46548831  -14693.47102098    2636.02297085\n                            1.75419415       2.01009519       0.18820066\n23246      720.0000     14292.78023665  -18025.45817671    2051.73388801\n                            2.82632243       0.95550567       0.33889498\n23246      719.0000     14122.26322965  -18081.82054152    2031.25426527\n                            2.85402200       0.92028827       0.34287876\n23246    -2880.0000      6652.58546066  -18825.60389076    1016.60260765\n                            3.69013844      -0.73211531       0.47012980\n\n1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    87\n2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  1058\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n88888     1440.0000      2742.55398832   -6079.67009123    -326.39012649\n                            1.94849765       1.21107268      -7.35619313\n88888        0.0000      2328.96975262   -5995.22051338    1719.97297192\n                            2.91207328      -0.98341796      -7.09081621\n88888      360.0000      2456.10706534   -6071.93855503    1222.89768554\n                            2.67939004      -0.44829081      -7.22879215\n88888     1080.0000      2663.08964352   -6115.48290885     196.40072867\n                            2.19612156       0.65241509      -7.36282415\n88888      720.0000      2567.56229695   -6112.50383923     713.96374435\n                            2.44024575       0.09810900      -7.31995926\n88888      719.0000      2415.04546550   -6103.57181162    1151.06777227\n                            2.64172961      -0.39588102      -7.24431138\n88888    -2880.0000       811.47797491   -4309.22921192    4950.18397460\n                            4.37308232      -4.52925216      -4.56259925\n\n1 00553U 63004A   77069.11186343 -.00000050 +00000-0 +00000-0 0 00056\n2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n  553     1440.0000     38666.00852401   16320.06355336    1390.26214785\n                           -0.95534267       2.38460447       1.69996307\n  553        0.0000     39371.88960849   14357.67432729       3.56059383\n                           -0.78417317       2.45190105       1.70300152\n  553      360.0000    -10430.26409143   34654.56621873   23921.02602917\n                           -2.83879733      -0.90289886       0.08194908\n  553     1080.0000     10636.62965551  -32115.18508783  -22388.43831871\n                            3.01460343       1.03226685      -0.04110622\n  553      720.0000    -40584.37401875  -10916.51306065    2408.92105255\n                            0.67194594      -2.47531856      -1.69215116\n  553      719.0000    -40624.29934803  -10767.89279867    2510.42520728\n                            0.65893063      -2.47879395      -1.69136266\n  553    -2880.0000     40358.16947683   10286.74194270   -2763.22558192\n                           -0.43103606       2.56117493       1.69059979\n\n1 98003U          01079.82102009 0.00000000  00000-0 +00000+0 0    01\n2 98003  15.5935 354.8013 0015600 263.1079  96.8986  0.99816000    03\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n98003     1440.0000     42094.43874734   -4162.63317801     -92.11251765\n                            0.29731062       2.94167365       0.82466681\n98003        0.0000     42138.92218986   -3695.09300700      36.83858364\n                            0.26211534       2.94493828       0.82474875\n98003      360.0000      3816.94976897   40619.28630698   11380.14550089\n                           -3.05212619       0.28557745       0.00228001\n98003     1080.0000     -4017.34782962  -40477.81421130  -11345.46738327\n                            3.05972108      -0.30236485      -0.00667726\n98003      720.0000    -42078.79869336    4169.84032031      95.55774114\n                           -0.28895565      -2.94353633      -0.82500563\n98003      719.0000    -42061.06251637    4346.40846620     145.05553205\n                           -0.30226340      -2.94218937      -0.82496759\n98003    -2880.0000     42211.20422977   -2757.46203195     295.45498207\n                            0.19153724       2.95030553       0.82456479\n\n1 99331U 95586 A  01083.00000000  .00000000  00000-0  00000+0 0    02\n2 99331   2.0413  66.6424 0042740 166.0448 281.2000  1.00273142    04\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n99331     1440.0000    -37967.20007787   18188.54928284    1490.04691741\n                           -1.31786034      -2.78108633       0.00497201\n99331        0.0000    -37650.29239730   18842.51288431    1488.61754716\n                           -1.36547168      -2.75777441       0.00690546\n99331      360.0000    -18244.13481977  -37816.78394398      78.83403095\n                            2.77788965      -1.34339543      -0.10919525\n99331     1080.0000     18205.05681771   38226.11783331     -71.55581790\n                           -2.76381418       1.31337921       0.10832465\n99331      720.0000     38182.58653158  -17913.55930782   -1493.30077039\n                            1.31619112       2.77567162      -0.00502478\n99331      719.0000     38103.25227752  -18079.92503503   -1492.98404546\n                            1.32832823       2.76994505      -0.00549993\n99331    -2880.0000    -36982.00226763   20135.38091665    1484.30297813\n                           -1.45957012      -2.70863518       0.01067441\n\n1 99337U 96750A   01088.00000000  .00000000  00000-0  00000+0 0    07\n2 99337   6.1829 349.1711 0918991 187.9440 347.1192  1.00269427    03\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n99337     1440.0000    -36655.10977909   11362.03173985     455.96900256\n                           -0.92608924      -3.21432746      -0.35997337\n99337        0.0000    -36430.83156122   12108.20282917     539.55631417\n                           -0.98617001      -3.19517122      -0.35913115\n99337      360.0000     -3574.53338013  -41263.36760245   -4450.55948116\n                            3.06244248      -0.55314863       0.00394174\n99337     1080.0000     18299.51337777   39008.25661897    4512.66010157\n                           -2.81152115       1.02034041       0.05076290\n99337      720.0000     44763.86907633  -10494.10728220    -197.95581429\n                            0.68873260       2.70568360       0.30113860\n99337      719.0000     44722.21536405  -10656.36841511    -216.02165984\n                            0.69974345       2.70308111       0.30108769\n99337    -2880.0000    -35941.07714058   13582.00600072     706.72604147\n                           -1.10477292      -3.15343148      -0.35704600\n\n1 24873U 97034E   02082.49700151  .00007707  00000-0  80741-3 0  6717\n2 24873  86.3901 131.0630 0010160 338.7719  21.3095 14.89420428254310\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24873     1440.0000     -3170.36218705    4147.28608625   -4630.10112111\n                           -3.53759777       3.58053394       5.63682354\n24873        0.0000     -4581.41284523    5258.61084472       0.13120073\n                           -0.35532106      -0.31679238       7.55020536\n24873      360.0000      1154.60502384    -670.29871734   -6855.07085843\n                           -4.79972247       5.66175625      -1.35462785\n24873     1080.0000     -2633.05049869    2482.40755471    5958.60901423\n                            4.03798855      -5.06780188       3.89613866\n24873      720.0000      4157.11952736   -5047.38360362    2456.26467926\n                            2.08212434      -1.72942731      -7.05069815\n24873      719.0000      4023.52709308   -4933.06375726    2873.82317083\n                            2.36940346      -2.07990992      -6.86301350\n24873    -2880.0000      -844.68212043     286.22703339    6914.24678007\n                            4.98354396      -5.61903649       0.84742770\n\n1 24875U 96051J   02082.46969260  .00045815  00000-0  12983-1 0   540\n2 24875  71.1295 245.1704 0037175  68.5321 292.0237 14.44819470284117\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24875     1440.0000      3727.97388868    5657.05158335    2196.20530149\n                           -0.91008388       3.24025401      -6.68298737\n24875        0.0000     -2985.35799132   -6456.57434435       5.43144043\n                            2.21461091      -0.98130257       7.09050046\n24875      360.0000       954.42359398    5539.03869291   -4419.41213767\n                           -3.73131396      -3.60392820      -5.34789246\n24875     1080.0000     -3385.36425424   -2309.38161890   -5850.47516091\n                           -1.78554526      -6.30701412       3.55127838\n24875      720.0000      1525.50746008   -2036.85257183    6614.96286888\n                            3.59899132       6.48806129       1.17208193\n24875      719.0000      1306.65238603   -2421.78994445    6531.38238635\n                            3.69374045       6.33889898       1.61303838\n24875    -2880.0000      -433.70797548   -5513.40720019    4444.41457039\n                            3.44271869       4.03963766       5.31126150\n\n1 24876U 97035A   02078.28561105 +.00000010 +00000-0 +00000-0 0 09256\n2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24876     1440.0000      2099.41349937   26412.90516367     804.49807544\n                           -2.18932651       0.07620057       3.20431913\n24876        0.0000      2625.97440180   26378.11936794       4.93598359\n                           -2.17691797       0.21563137       3.20640808\n24876      360.0000     -2497.91488385  -26492.75727038    -215.05161132\n                            2.17194116      -0.17926961      -3.19391301\n24876     1080.0000     -2235.67544642  -26509.99535031    -613.34346165\n                            2.17808366      -0.11008692      -3.19285598\n24876      720.0000      2363.06016716   26399.81509304     404.83568315\n                           -2.18346040       0.14593024       3.20589849\n24876      719.0000      2493.97473666   26390.04248290     212.46848448\n                           -2.18034211       0.17982242       3.20629487\n24876    -2880.0000      3668.52647508   26205.35817905   -1594.36971909\n                           -2.14399113       0.49367668       3.19776010\n\n1 24138U 94029GM  02078.77267326 +.00026592 +00000-0 +63509-2 0 09707\n2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24138     1440.0000     -2644.46044349     711.84109121    6816.75919791\n                           -6.75946029      -1.13271605      -2.41488958\n24138        0.0000      6960.83886410     916.39636032      -0.35227799\n                           -0.00283653       1.12124163       7.53007134\n24138      360.0000     -6575.12211937   -1248.69051257   -2759.35846726\n                            3.12213925      -0.59446457      -6.66420362\n24138     1080.0000     -1260.83554616   -1167.51006970   -6834.94030738\n                            7.42952369       0.68738953      -1.31119896\n24138      720.0000      3833.67603624    1356.98826915    5941.80488966\n                           -6.10632098      -0.12493292       4.21033574\n24138      719.0000      4192.46252358    1361.87543045    5677.91444852\n                           -5.84922873      -0.03784350       4.58349220\n24138    -2880.0000     -5379.10149179    -140.63187806    5028.19534452\n                           -4.82984652      -1.57769725      -5.17470482\n\n1 24151U 94029HA  02078.68402149 +.00005369 +00000-0 +11280-2 0 09568\n2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24151     1440.0000     -6968.30877120    -929.52208053    1034.74250336\n                           -0.80683886      -1.16641712      -7.35690383\n24151        0.0000      7014.87041848    1202.45499306       0.64793182\n                           -0.04978932       1.04323018       7.40731873\n24151      360.0000     -5152.24684405   -1503.50157586   -4520.64323072\n                            5.06324107       0.04552212      -5.64402671\n24151     1080.0000      4676.15154374      -1.83212884   -5242.49623820\n                            5.59390309       1.57561016       4.85528798\n24151      720.0000       375.74981624    1074.45821808    7136.52489241\n                           -7.25295787      -1.10477013       0.54189381\n24151      719.0000       809.94234149    1138.67114921    7090.50151769\n                           -7.21557010      -1.03498445       0.99168739\n24151    -2880.0000      6855.50805677    1570.68533265    1126.34405871\n                           -1.25338851       0.78905151       7.32628675\n\n1 24156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\n2 24156  82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24156     1440.0000      4989.95103335    -991.96048211   -4694.62995315\n                           -4.75190381       2.38718598      -5.51355655\n24156        0.0000     -6806.70697240    2154.76645090      -1.33970314\n                           -0.42401840      -0.92331212       7.36329634\n24156      360.0000      4072.30634864    -505.08877357   -5572.74271903\n                           -5.74652368       2.48040035      -4.41726446\n24156     1080.0000     -6585.62318688    2365.50422511   -1320.83679750\n                           -1.76181618      -0.46018527       7.22613299\n24156      720.0000      1912.45112070   -1587.96387581    6693.48120108\n                            6.74703272      -1.83838893      -2.49156574\n24156      719.0000      1504.14766273   -1474.62142897    6829.72995099\n                            6.85862162      -1.93841503      -2.04871469\n24156    -2880.0000       275.97078586     901.03018894   -6871.10287465\n                           -7.35389459       2.07610218      -0.09182194\n\n1 07276U 74026A   02076.77143097 -.00000040 +00000-0 +10000-3 0 09926\n2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 7276     1440.0000     11679.26473158   15504.27588846   31001.50846115\n                           -1.44086891       0.74720602       1.72238289\n 7276        0.0000     12929.17822712     737.31896601      -0.93772206\n                            3.66517540       2.61185503       5.01178899\n 7276      360.0000     -8415.24017849   16549.03285541   35497.28095363\n                           -1.60749073      -0.53051900      -0.91712311\n 7276     1080.0000    -15186.86012009   12823.64831401   28488.61304853\n                           -1.17308269      -1.05510209      -2.06539757\n 7276      720.0000     16901.65208240   10351.66308625   19617.59800800\n                           -0.59625193       1.47294504       3.14133784\n 7276      719.0000     16936.85061239   10262.95529869   19428.46499770\n                           -0.57745384       1.48439746       3.16303871\n 7276    -2880.0000     -4858.50529290   17447.37140077   36997.57469065\n                           -1.69260423      -0.31621755      -0.44065120\n\n1 07191U 73086EZ  02076.63595131 -.00000031  00000-0  10000-3 0  9045\n2 07191 102.1490 334.0121 0253607  54.7437 307.7251 12.10431881825991\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 7191     1440.0000      5197.68379504   -3532.40188648    4642.94280275\n                           -4.52981807       0.79135016       5.57991435\n 7191        0.0000      7095.62534394   -3459.54977223       2.65766867\n                           -0.79920008      -1.28572174       6.99886910\n 7191      360.0000      6876.82395295   -3614.28549445    1242.00621199\n                           -1.80950045      -0.77766069       6.90918302\n 7191     1080.0000      5917.38908180   -3650.54888754    3593.33932356\n                           -3.70227403       0.27502090       6.19225553\n 7191      720.0000      6481.41080392   -3678.31238626    2449.51628455\n                           -2.78652792      -0.25275130       6.63881188\n 7191      719.0000      6638.89853614   -3657.68854846    2047.73362803\n                           -2.46167103      -0.43439031       6.75039533\n 7191    -2880.0000      3051.67782955     225.45893096   -7549.72713780\n                            5.53973976      -3.48618620       2.28442985\n\n1 07192U 73086FA  02077.02656547 -.00000031  00000-0  10000-3 0  6091\n2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 7192     1440.0000      2735.71311905    6452.74755933   -3628.13173013\n                            0.29924939      -3.70237520      -6.23622609\n 7192        0.0000     -2551.23569421   -8127.44238653       0.87987080\n                           -1.40913462       0.23779939       6.57529016\n 7192      360.0000     -1039.48185564   -6786.59096842   -4710.94007931\n                           -2.34291375      -3.69510078       5.31633780\n 7192     1080.0000      2333.10979200    2362.32508796   -7199.46956782\n                           -1.35450606      -6.54040439      -2.74857242\n 7192      720.0000       848.53025315   -2793.33496212   -7554.32974481\n                           -2.36766420      -6.41802990       1.80245163\n 7192      719.0000       989.38243489   -2404.65801357   -7652.21700164\n                           -2.32628871      -6.53504480       1.45945499\n 7192    -2880.0000       230.46423657   -3990.71376868   -7081.27099350\n                           -2.68412105      -5.84908358       2.87113784\n\n1 11054U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\n2 11054  62.4872 198.8383 0057450 192.9653 166.8284  1.93504662169140\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11054     1440.0000    -25388.34079710   -3403.40500597   -9531.93443534\n                           -0.90793207      -2.01537981       3.10310036\n11054        0.0000    -25891.75008739   -8827.17634454      -7.11543233\n                            0.56259784      -1.66520502       3.37653764\n11054      360.0000     25891.99345977    7409.73868823    2577.67984854\n                           -0.18479992       1.80406882      -3.39491593\n11054     1080.0000     25613.56652955    4649.87873299    7410.72787690\n                            0.56951792       1.98044940      -3.24876400\n11054      720.0000    -26175.21689483   -6242.60712582   -4868.83227692\n                           -0.17573206      -1.87873379       3.30745510\n11054      719.0000    -26163.75460152   -6129.66517687   -5067.10706616\n                           -0.20634035      -1.88596920       3.30164344\n11054    -2880.0000    -14933.48469609  -14743.73993336   17504.98769057\n                            3.00456330      -0.25365150       2.32952622\n\n1 11057U 78095A   02077.63138055  .00000149  00000-0  10000-3 0  5309\n2 11057  63.7422 203.7241 6717022 282.5815  14.8175  2.34176921180342\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11057     1440.0000      7487.87611216  -15287.29467029   34493.07590041\n                            1.86179494       0.47483499       0.62605389\n11057        0.0000    -10507.51193223   -4611.31451017       0.19783099\n                           -2.18140918      -4.00411475       5.66127724\n11057      360.0000     20970.88101088   -7510.36265833   31022.77017800\n                            0.99148126       1.17155611      -1.36986859\n11057     1080.0000     23275.29351376     644.95594684   17692.25539266\n                           -0.47864173       1.36230389      -2.92144014\n11057      720.0000     -4731.80410387  -15608.68113270   25160.74838285\n                            1.90090167      -0.49586326       2.46683975\n11057      719.0000     -4845.71559110  -15578.51400495   25012.06196398\n                            1.89663033      -0.50977445       2.48923053\n11057    -2880.0000      5859.91793323  -15633.31281630   33804.16988953\n                            1.90326131       0.38485257       0.85574795\n\n1 25989U 99066A   02080.37500000  .00000000  00000-0  00000+0 0  4082\n2 25989  34.6613 172.6861 7995138 107.4591   3.7150  0.50103982  1159\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n25989     1440.0000    -25416.31130478   97972.48561814  -65060.88827233\n                           -0.78954842      -0.14306696       0.17001526\n25989        0.0000     13446.88594396   -7340.90106369    3841.96143867\n                            5.25343486       3.15492823      -2.63964361\n25989      360.0000     27779.82527478   52562.51176863  -38630.70190521\n                           -0.55664353       1.86219779      -1.23020626\n25989     1080.0000     -7306.20327748   95752.02176493  -65174.16312805\n                           -0.87298261       0.35585797      -0.16572419\n25989      720.0000     11590.33887760   81938.35349884  -57377.90111303\n                           -0.85404887       0.94997888      -0.57613966\n25989      719.0000     11641.57460814   81881.29555505  -57343.27701803\n                           -0.85377652       0.95189906      -0.57748483\n25989    -2880.0000     11272.09487443   -8515.50280915    4864.79909744\n                            5.79043510       2.81320525      -2.44112033\n\n1 25990U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\n2 25990  44.2277 151.3168 8216838 138.1402 356.4324  0.55308221  1445\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n25990     1440.0000    -52618.00493523   87304.59689650  -50015.63309567\n                           -0.57621934      -0.27322941       0.50620611\n25990        0.0000     -7091.02772480   -7636.71306513    9880.83142057\n                            6.57104133      -2.04322973      -1.34121281\n25990      360.0000      6941.12895930   47572.57987853  -44038.73006094\n                           -1.08656024       1.87029231      -1.09113296\n25990     1080.0000    -37147.20354816   87628.63341664  -57597.53860234\n                           -0.84368212       0.24906550       0.18441609\n25990      720.0000    -16647.88296791   75877.09428533  -57189.29131723\n                           -1.04294149       0.86860516      -0.25294533\n25990      719.0000    -16585.29849772   75824.91738346  -57174.06802345\n                           -1.04338459       0.87062791      -0.25447089\n25990    -2880.0000    -51622.41035047   33904.99902952   -4876.35838422\n                            1.10240406      -1.97705598       1.17712115\n\n1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    87\n2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  1058\nNear-Earth type Ephemeris (SGP ) selected:\nEphem:SGP    Tsince         X/Xdot           Y/Ydot           Z/Zdot\n88888     1440.0000      2742.85536090   -6079.13550910    -328.86284909\n                            1.94707845       1.21346317      -7.35499922\n88888        0.0000      2328.96608372   -5995.21604006    1719.97873587\n                            2.91110102      -0.98164033      -7.09049933\n88888      360.0000      2456.00667036   -6071.94243808    1222.95881238\n                            2.67852067      -0.44705739      -7.22800586\n88888     1080.0000      2663.03277851   -6115.37384735     195.73637767\n                            2.19531669       0.65334243      -7.36169154\n88888      720.0000      2567.39619789   -6112.49733464     713.97377545\n                            2.43952312       0.09885196      -7.31889680\n88888      719.0000      2414.88066271   -6103.56671515    1151.08195539\n                            2.64086106      -0.39464444      -7.24352530\n88888    -2880.0000       777.57978207   -4273.14463448    4985.36908569\n                            4.38355864      -4.57458675      -4.51353652\n\n1 07191U 73086EZ  02076.63595131 -.00000031  00000-0  10000-3 0  9045\n2 07191 102.1490 334.0121 0253607  54.7437 307.7251 12.10431881825991\nNear-Earth type Ephemeris (SGP ) selected:\nEphem:SGP    Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 7191     1440.0000      5197.66873608   -3532.31805398    4643.01987432\n                           -4.52897204       0.79041016       5.58191897\n 7191        0.0000      7095.62120595   -3459.54927659       2.66401972\n                           -0.79910050      -1.28559271       6.99812407\n 7191      360.0000      6876.82512150   -3614.26385689    1242.03845145\n                           -1.80869892      -0.77793181       6.90867534\n 7191     1080.0000      5917.38393015   -3650.48327444    3593.40814162\n                           -3.70098826       0.27411893       6.19332356\n 7191      720.0000      6481.41138382   -3678.26827404    2449.56983740\n                           -2.78527757      -0.25340334       6.63896277\n 7191      719.0000      6638.90133477   -3657.64485481    2047.78825011\n                           -2.46053716      -0.43492114       6.75028306\n 7191    -2880.0000      3051.43789296     225.52678160   -7549.82076196\n                            5.54201639      -3.48810492       2.28652461\n\n1 24151U 94029HA  02078.68402149 +.00005369 +00000-0 +11280-2 0 09568\n2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215\nNear-Earth type Ephemeris (SGP ) selected:\nEphem:SGP    Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24151     1440.0000     -6968.32932565    -929.40448044    1034.64126944\n                           -0.80580417      -1.16615531      -7.35630983\n24151        0.0000      7014.86418693    1202.45521289       0.65699104\n                           -0.04981154       1.04310209       7.40643748\n24151      360.0000     -5152.23932194   -1503.47537247   -4520.65312876\n                            5.06208677       0.04482191      -5.64742354\n24151     1080.0000      4676.17981006      -1.89119338   -5242.47744425\n                            5.59374094       1.57609633       4.85948272\n24151      720.0000       375.73095005    1074.44783207    7136.51617056\n                           -7.25840064      -1.10546387       0.54275292\n24151      719.0000       809.92451644    1138.65695987    7090.49457660\n                           -7.22083372      -1.03555127       0.99323935\n24151    -2880.0000      6855.43779076    1570.95203705    1126.35885487\n                           -1.25231806       0.78915162       7.32573752\n\n1 11801U          80230.29629788  .01431103  00000-0  14311-1       2\n2 11801U 46.7916 230.4354 7318036  47.4722  10.4117  2.28537848     2\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11801     1440.0000      9420.07877124   33847.21210751  -15391.07637268\n                           -1.11986083       0.85410171      -1.49506971\n11801        0.0000      7469.47832570     415.99601049    5829.64070389\n                            5.11402275       6.44403160      -0.18296530\n11801      360.0000     -3337.39439914   32351.38232011  -24658.64280341\n                           -1.30200749      -1.15603002      -0.28164871\n11801     1080.0000    -10151.60323755   22223.69587529  -23392.40546558\n                           -1.00112488      -2.33532743       0.76987728\n11801      720.0000     14226.54285494   24236.08413793   -4856.20981132\n                           -0.33951710       2.65315365      -2.08114247\n11801      719.0000     14246.55591480   24076.39086885   -4731.31979056\n                           -0.32472236       2.67823919      -2.08611617\n11801    -2880.0000      -223.35624900   35795.86991125  -24125.87317194\n                           -1.28307664      -0.69669553      -0.59333659\n\n1 23581U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\n2 23581   1.1236  93.7945 0005741 214.4722 151.5103  1.00270260 23672\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n23581     1440.0000     -7871.56853853   41445.01028733      99.12526295\n                           -3.01873843      -0.57266832       0.05898135\n23581        0.0000     -7160.41019110   41573.61598173      85.48240526\n                           -3.02812759      -0.52082969       0.05893423\n23581      360.0000    -41536.21579580   -7273.04718857     809.12524992\n                            0.53157531      -3.02755942      -0.00641868\n23581     1080.0000     41434.84287574    7708.64529580    -808.83999233\n                           -0.56086098       3.02392827       0.00697913\n23581      720.0000      7464.73793892  -41476.91032627     -91.39443550\n                            3.02688554       0.54571792      -0.05902770\n23581      719.0000      7283.05704075  -41509.25513334     -87.85184603\n                            3.02924171       0.53245970      -0.05905626\n23581    -2880.0000     -5731.90933914   41794.16078256      56.75309470\n                           -3.04423869      -0.41669898       0.05871630\n\n1 11871U 80057A   01309.36911127 -.00000499 +00000-0 +10000-3 0 08380\n2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11871     1440.0000     43302.80205821    1671.33337009     708.84290227\n                           -0.00007483       0.70004183       1.69580723\n11871        0.0000     43281.63009884    1435.59001959       3.14486765\n                            0.08891078       0.70328553       1.69655546\n11871      360.0000     -8889.54166788    1785.40677378    5032.32456780\n                           -2.42809846      -2.92244080      -6.88563770\n11871     1080.0000     -9330.69533866    1153.81200018    3522.58325526\n                           -1.69743737      -3.03901748      -7.22910421\n11871      720.0000     43296.78149493    1553.63423509     356.07045290\n                            0.04438893       0.70172180       1.69636906\n11871      719.0000     43293.73109183    1511.51575886     254.29738462\n                            0.05712382       0.70217258       1.69645888\n11871    -2880.0000     43129.19936626     959.66710935   -1409.52575892\n                            0.26766933       0.70838993       1.69353546\n\n1 09931U 77029A   01309.17453186 -.00000329 +00000-0 +10000-3 0 05967\n2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 9931     1440.0000     -9778.48987133  -16465.90211417   -3968.19260205\n                            4.55266487       0.54373338       2.22551193\n 9931        0.0000     -1449.49218871  -13892.08044897      -3.10530650\n                            5.30771421       2.82012032       2.48432921\n 9931      360.0000    -37387.46937492   14074.89826166  -19247.30542923\n                           -0.81611892      -1.55548092      -0.32311993\n 9931     1080.0000    -36654.75223572   15309.13449317  -18957.41610972\n                           -0.95886046      -1.49951537      -0.39542419\n 9931      720.0000     -5779.28997617  -15646.31216122   -2044.68222537\n                            4.98442796       1.44324070       2.39333905\n 9931      719.0000     -6077.47588018  -15730.57952632   -2187.96602288\n                            4.95506237       1.36555165       2.38284936\n 9931    -2880.0000      7311.67417756    7867.60640719    3240.01029370\n                           -2.39893349       6.94310651      -1.52787421\n\n1 12032U 80085A   01309.42683181  .00000182  00000-0  10000-3 0  3499\n2 12032  65.2329  86.7607 7086222 172.0967 212.4632  2.00879501101699\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n12032     1440.0000      2332.67684843   44018.77243136     560.22303913\n                           -0.72056504      -0.57801149       1.48601722\n12032        0.0000      2511.95138081   44229.85772465       6.90735682\n                           -0.71520971      -0.50292459       1.48609834\n12032      360.0000      8456.13178456    7596.47415381  -17345.64200705\n                            0.98145741       4.47229845      -1.56375719\n12032     1080.0000      8642.68187872    8419.98150257  -17628.39595336\n                            0.91731844       4.40681207      -1.42358978\n12032      720.0000      2422.62111468   44127.86585308     283.58703413\n                           -0.71788553      -0.54036416       1.48617770\n12032      719.0000      2465.67705412   44159.91318851     194.42947846\n                           -0.71720917      -0.52814665       1.48624365\n12032    -2880.0000      2862.79943427   44567.42649491   -1098.68468651\n                           -0.70456283      -0.35496244       1.48350597\n\n1 13446U 82083E   01283.10818257  .00098407  45745-7  54864-3 0  6240\n2 13446  62.1717  83.8458 7498877 273.9677 320.2568  2.06357523137203\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n13446     1440.0000     -2125.88142754  -12947.66502817    1283.41745547\n                            2.99883067       3.45765109      -4.92196263\n13446        0.0000     -8853.93990719  -17327.36676474   13139.86856630\n                            2.05271983       0.46128459      -3.77277615\n13446      360.0000    -20299.70180013    4920.93352717   39241.59207738\n                           -0.47132845      -1.43645540       0.59062252\n13446     1080.0000    -20841.89139093    2979.73374086   39863.34261809\n                           -0.34977240      -1.46022047       0.35241917\n13446      720.0000     -5846.21876690  -16107.67410473    7674.94185833\n                            2.47196537       1.42588337      -4.36101922\n13446      719.0000     -5993.90664751  -16191.56920641    7935.72755383\n                            2.45095181       1.36858556      -4.33328807\n13446    -2880.0000    -16514.44453543  -15495.04420318   28136.69038241\n                            1.07537700      -0.89797779      -2.19970996\n\n1 23246U 91015G   01311.70347086  .00004957  00000-0  43218-2 0  8190\n2 23246   7.1648 263.6949 5661268 241.8299  50.5793  4.44333001129208\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n23246     1440.0000     21838.75432359   -9448.92449345    2882.78618298\n                            0.50386494       2.73950392       0.01915119\n23246        0.0000     -1570.27461571  -14416.86458350       7.00840579\n                            4.09713683      -3.51695816       0.56345678\n23246      360.0000      7130.55602310  -18534.41345052    1164.06716757\n                            3.73258667      -0.64402643       0.47713833\n23246     1080.0000     19322.51132279  -14694.47518853    2646.50178227\n                            1.75313528       2.00940835       0.18819521\n23246      720.0000     14302.94654416  -18022.87491164    2062.60799238\n                            2.82489986       0.95613920       0.33936666\n23246      719.0000     14132.58308794  -18079.18162736    2042.12566658\n                            2.85260295       0.92097151       0.34336744\n23246    -2880.0000      6673.73527112  -18825.43286661    1027.53767406\n                            3.68910987      -0.72788220       0.47136626\n\n1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    87\n2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  1058\nNear-Earth type Ephemeris (SGP8) selected:\nEphem:SGP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n88888     1440.0000      2743.29435330   -6078.90669382    -329.74076507\n                            1.94680613       1.21500821      -7.35625567\n88888        0.0000      2328.87330160   -5995.21266135    1720.04860850\n                            2.91210628      -0.98353806      -7.09081552\n88888      360.0000      2456.04639018   -6071.90492842    1222.83939295\n                            2.67936164      -0.44820689      -7.22888597\n88888     1080.0000      2663.49578474   -6115.18153477     194.62560022\n                            2.19525089       0.65453937      -7.36308979\n88888      720.0000      2567.68504071   -6112.40874085     713.29056655\n                            2.43992421       0.09894177      -7.32018816\n88888      719.0000      2415.18557698   -6103.52890192    1150.40884796\n                            2.64142685      -0.39506444      -7.24458496\n88888    -2880.0000       820.91948977   -4319.03528436    4940.20423767\n                            4.37073001      -4.51683717      -4.57704872\n\n1 00553U 63004A   77069.11186343 -.00000050 +00000-0 +00000-0 0 00056\n2 00553 033.5060 020.0500 0337500 270.6300 085.5000 01.00948389000106\nDeep-Space type Ephemeris (SDP8) selected:\nEphem:SDP8   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n  553     1440.0000     38665.99209639   16320.09343680    1390.38386144\n                           -0.95534613       2.38459407       1.69997574\n  553        0.0000     39371.87433765   14357.71188366       3.67164552\n                           -0.78417677       2.45189100       1.70301485\n  553      360.0000    -10430.23639914   34654.47913595   23921.22576863\n                           -2.83879331      -0.90289819       0.08194935\n  553     1080.0000     10636.57831004  -32115.05510514  -22388.58911206\n                            3.01460753       1.03226846      -0.04110713\n  553      720.0000    -40584.37656205  -10916.48555626    2409.05615369\n                            0.67193646      -2.47531248      -1.69216320\n  553      719.0000    -40624.30145743  -10767.86568235    2510.56100536\n                            0.65892114      -2.47878785      -1.69137464\n  553    -2880.0000     40358.15660775   10286.79429280   -2763.13836900\n                           -0.43103967       2.56116578       1.69061427\n\n1 98003U          01079.82102009 0.00000000  00000-0 +00000+0 0    01\n2 98003  15.5935 354.8013 0015600 263.1079  96.8986  0.99816000    03\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n98003     1440.0000     42094.43874734   -4162.63317801     -92.11251765\n                            0.29731062       2.94167365       0.82466681\n98003        0.0000     42138.92218986   -3695.09300700      36.83858364\n                            0.26211534       2.94493828       0.82474875\n98003      360.0000      3816.94976897   40619.28630698   11380.14550089\n                           -3.05212619       0.28557745       0.00228001\n98003     1080.0000     -4017.34782962  -40477.81421130  -11345.46738327\n                            3.05972108      -0.30236485      -0.00667726\n98003      720.0000    -42078.79869336    4169.84032031      95.55774114\n                           -0.28895565      -2.94353633      -0.82500563\n98003      719.0000    -42061.06251637    4346.40846620     145.05553205\n                           -0.30226340      -2.94218937      -0.82496759\n98003    -2880.0000     42211.20422977   -2757.46203195     295.45498207\n                            0.19153724       2.95030553       0.82456479\n\n1 99331U 95586 A  01083.00000000  .00000000  00000-0  00000+0 0    02\n2 99331   2.0413  66.6424 0042740 166.0448 281.2000  1.00273142    04\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n99331     1440.0000    -37967.20007787   18188.54928284    1490.04691741\n                           -1.31786034      -2.78108633       0.00497201\n99331        0.0000    -37650.29239730   18842.51288431    1488.61754716\n                           -1.36547168      -2.75777441       0.00690546\n99331      360.0000    -18244.13481977  -37816.78394398      78.83403095\n                            2.77788965      -1.34339543      -0.10919525\n99331     1080.0000     18205.05681771   38226.11783331     -71.55581790\n                           -2.76381418       1.31337921       0.10832465\n99331      720.0000     38182.58653158  -17913.55930782   -1493.30077039\n                            1.31619112       2.77567162      -0.00502478\n99331      719.0000     38103.25227752  -18079.92503503   -1492.98404546\n                            1.32832823       2.76994505      -0.00549993\n99331    -2880.0000    -36982.00226763   20135.38091665    1484.30297813\n                           -1.45957012      -2.70863518       0.01067441\n\n1 99337U 96750A   01088.00000000  .00000000  00000-0  00000+0 0    07\n2 99337   6.1829 349.1711 0918991 187.9440 347.1192  1.00269427    03\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n99337     1440.0000    -36655.10977909   11362.03173985     455.96900256\n                           -0.92608924      -3.21432746      -0.35997337\n99337        0.0000    -36430.83156122   12108.20282917     539.55631417\n                           -0.98617001      -3.19517122      -0.35913115\n99337      360.0000     -3574.53338013  -41263.36760245   -4450.55948116\n                            3.06244248      -0.55314863       0.00394174\n99337     1080.0000     18299.51337777   39008.25661897    4512.66010157\n                           -2.81152115       1.02034041       0.05076290\n99337      720.0000     44763.86907633  -10494.10728220    -197.95581429\n                            0.68873260       2.70568360       0.30113860\n99337      719.0000     44722.21536405  -10656.36841511    -216.02165984\n                            0.69974345       2.70308111       0.30108769\n99337    -2880.0000    -35941.07714058   13582.00600072     706.72604147\n                           -1.10477292      -3.15343148      -0.35704600\n\n1 24873U 97034E   02082.49700151  .00007707  00000-0  80741-3 0  6717\n2 24873  86.3901 131.0630 0010160 338.7719  21.3095 14.89420428254310\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24873     1440.0000     -3170.36218705    4147.28608625   -4630.10112111\n                           -3.53759777       3.58053394       5.63682354\n24873        0.0000     -4581.41284523    5258.61084472       0.13120073\n                           -0.35532106      -0.31679238       7.55020536\n24873      360.0000      1154.60502384    -670.29871734   -6855.07085843\n                           -4.79972247       5.66175625      -1.35462785\n24873     1080.0000     -2633.05049869    2482.40755471    5958.60901423\n                            4.03798855      -5.06780188       3.89613866\n24873      720.0000      4157.11952736   -5047.38360362    2456.26467926\n                            2.08212434      -1.72942731      -7.05069815\n24873      719.0000      4023.52709308   -4933.06375726    2873.82317083\n                            2.36940346      -2.07990992      -6.86301350\n24873    -2880.0000      -844.68212043     286.22703339    6914.24678007\n                            4.98354396      -5.61903649       0.84742770\n\n1 24875U 96051J   02082.46969260  .00045815  00000-0  12983-1 0   540\n2 24875  71.1295 245.1704 0037175  68.5321 292.0237 14.44819470284117\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24875     1440.0000      3727.97388868    5657.05158335    2196.20530149\n                           -0.91008388       3.24025401      -6.68298737\n24875        0.0000     -2985.35799132   -6456.57434435       5.43144043\n                            2.21461091      -0.98130257       7.09050046\n24875      360.0000       954.42359398    5539.03869291   -4419.41213767\n                           -3.73131396      -3.60392820      -5.34789246\n24875     1080.0000     -3385.36425424   -2309.38161890   -5850.47516091\n                           -1.78554526      -6.30701412       3.55127838\n24875      720.0000      1525.50746008   -2036.85257183    6614.96286888\n                            3.59899132       6.48806129       1.17208193\n24875      719.0000      1306.65238603   -2421.78994445    6531.38238635\n                            3.69374045       6.33889898       1.61303838\n24875    -2880.0000      -433.70797548   -5513.40720019    4444.41457039\n                            3.44271869       4.03963766       5.31126150\n\n1 24876U 97035A   02078.28561105 +.00000010 +00000-0 +00000-0 0 09256\n2 24876 055.7160 084.3160 0019362 356.8391 003.1921 02.00573292034149\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24876     1440.0000      2099.41349937   26412.90516367     804.49807544\n                           -2.18932651       0.07620057       3.20431913\n24876        0.0000      2625.97440180   26378.11936794       4.93598359\n                           -2.17691797       0.21563137       3.20640808\n24876      360.0000     -2497.91488385  -26492.75727038    -215.05161132\n                            2.17194116      -0.17926961      -3.19391301\n24876     1080.0000     -2235.67544642  -26509.99535031    -613.34346165\n                            2.17808366      -0.11008692      -3.19285598\n24876      720.0000      2363.06016716   26399.81509304     404.83568315\n                           -2.18346040       0.14593024       3.20589849\n24876      719.0000      2493.97473666   26390.04248290     212.46848448\n                           -2.18034211       0.17982242       3.20629487\n24876    -2880.0000      3668.52647508   26205.35817905   -1594.36971909\n                           -2.14399113       0.49367668       3.19776010\n\n1 24138U 94029GM  02078.77267326 +.00026592 +00000-0 +63509-2 0 09707\n2 24138 081.5941 007.5003 0284757 314.7693 043.0606 14.31683973298361\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24138     1440.0000     -2644.46044349     711.84109121    6816.75919791\n                           -6.75946029      -1.13271605      -2.41488958\n24138        0.0000      6960.83886410     916.39636032      -0.35227799\n                           -0.00283653       1.12124163       7.53007134\n24138      360.0000     -6575.12211937   -1248.69051257   -2759.35846726\n                            3.12213925      -0.59446457      -6.66420362\n24138     1080.0000     -1260.83554616   -1167.51006970   -6834.94030738\n                            7.42952369       0.68738953      -1.31119896\n24138      720.0000      3833.67603624    1356.98826915    5941.80488966\n                           -6.10632098      -0.12493292       4.21033574\n24138      719.0000      4192.46252358    1361.87543045    5677.91444852\n                           -5.84922873      -0.03784350       4.58349220\n24138    -2880.0000     -5379.10149179    -140.63187806    5028.19534452\n                           -4.82984652      -1.57769725      -5.17470482\n\n1 24151U 94029HA  02078.68402149 +.00005369 +00000-0 +11280-2 0 09568\n2 24151 082.0281 009.7261 0181752 265.0522 092.9956 14.49591905305215\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24151     1440.0000     -6968.30877120    -929.52208053    1034.74250336\n                           -0.80683886      -1.16641712      -7.35690383\n24151        0.0000      7014.87041848    1202.45499306       0.64793182\n                           -0.04978932       1.04323018       7.40731873\n24151      360.0000     -5152.24684405   -1503.50157586   -4520.64323072\n                            5.06324107       0.04552212      -5.64402671\n24151     1080.0000      4676.15154374      -1.83212884   -5242.49623820\n                            5.59390309       1.57561016       4.85528798\n24151      720.0000       375.74981624    1074.45821808    7136.52489241\n                           -7.25295787      -1.10477013       0.54189381\n24151      719.0000       809.94234149    1138.67114921    7090.50151769\n                           -7.21557010      -1.03498445       0.99168739\n24151    -2880.0000      6855.50805677    1570.68533265    1126.34405871\n                           -1.25338851       0.78905151       7.32628675\n\n1 24156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\n2 24156  82.1981 162.4355 0207818 237.7291 120.3519 14.63437792297579\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n24156     1440.0000      4989.95103335    -991.96048211   -4694.62995315\n                           -4.75190381       2.38718598      -5.51355655\n24156        0.0000     -6806.70697240    2154.76645090      -1.33970314\n                           -0.42401840      -0.92331212       7.36329634\n24156      360.0000      4072.30634864    -505.08877357   -5572.74271903\n                           -5.74652368       2.48040035      -4.41726446\n24156     1080.0000     -6585.62318688    2365.50422511   -1320.83679750\n                           -1.76181618      -0.46018527       7.22613299\n24156      720.0000      1912.45112070   -1587.96387581    6693.48120108\n                            6.74703272      -1.83838893      -2.49156574\n24156      719.0000      1504.14766273   -1474.62142897    6829.72995099\n                            6.85862162      -1.93841503      -2.04871469\n24156    -2880.0000       275.97078586     901.03018894   -6871.10287465\n                           -7.35389459       2.07610218      -0.09182194\n\n1 07276U 74026A   02076.77143097 -.00000040 +00000-0 +10000-3 0 09926\n2 07276 064.4058 003.2732 6887090 270.2374 017.8845 02.24788602077456\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 7276     1440.0000     11679.26473158   15504.27588846   31001.50846115\n                           -1.44086891       0.74720602       1.72238289\n 7276        0.0000     12929.17822712     737.31896601      -0.93772206\n                            3.66517540       2.61185503       5.01178899\n 7276      360.0000     -8415.24017849   16549.03285541   35497.28095363\n                           -1.60749073      -0.53051900      -0.91712311\n 7276     1080.0000    -15186.86012009   12823.64831401   28488.61304853\n                           -1.17308269      -1.05510209      -2.06539757\n 7276      720.0000     16901.65208240   10351.66308625   19617.59800800\n                           -0.59625193       1.47294504       3.14133784\n 7276      719.0000     16936.85061239   10262.95529869   19428.46499770\n                           -0.57745384       1.48439746       3.16303871\n 7276    -2880.0000     -4858.50529290   17447.37140077   36997.57469065\n                           -1.69260423      -0.31621755      -0.44065120\n\n1 07191U 73086EZ  02076.63595131 -.00000031  00000-0  10000-3 0  9045\n2 07191 102.1490 334.0121 0253607  54.7437 307.7251 12.10431881825991\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 7191     1440.0000      5197.68379504   -3532.40188648    4642.94280275\n                           -4.52981807       0.79135016       5.57991435\n 7191        0.0000      7095.62534394   -3459.54977223       2.65766867\n                           -0.79920008      -1.28572174       6.99886910\n 7191      360.0000      6876.82395295   -3614.28549445    1242.00621199\n                           -1.80950045      -0.77766069       6.90918302\n 7191     1080.0000      5917.38908180   -3650.54888754    3593.33932356\n                           -3.70227403       0.27502090       6.19225553\n 7191      720.0000      6481.41080392   -3678.31238626    2449.51628455\n                           -2.78652792      -0.25275130       6.63881188\n 7191      719.0000      6638.89853614   -3657.68854846    2047.73362803\n                           -2.46167103      -0.43439031       6.75039533\n 7191    -2880.0000      3051.67782955     225.45893096   -7549.72713780\n                            5.53973976      -3.48618620       2.28442985\n\n1 07192U 73086FA  02077.02656547 -.00000031  00000-0  10000-3 0  6091\n2 07192 102.1562 252.5740 0443592 220.8713 135.8303 11.59390485200610\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n 7192     1440.0000      2735.71311905    6452.74755933   -3628.13173013\n                            0.29924939      -3.70237520      -6.23622609\n 7192        0.0000     -2551.23569421   -8127.44238653       0.87987080\n                           -1.40913462       0.23779939       6.57529016\n 7192      360.0000     -1039.48185564   -6786.59096842   -4710.94007931\n                           -2.34291375      -3.69510078       5.31633780\n 7192     1080.0000      2333.10979200    2362.32508796   -7199.46956782\n                           -1.35450606      -6.54040439      -2.74857242\n 7192      720.0000       848.53025315   -2793.33496212   -7554.32974481\n                           -2.36766420      -6.41802990       1.80245163\n 7192      719.0000       989.38243489   -2404.65801357   -7652.21700164\n                           -2.32628871      -6.53504480       1.45945499\n 7192    -2880.0000       230.46423657   -3990.71376868   -7081.27099350\n                           -2.68412105      -5.84908358       2.87113784\n\n1 11054U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\n2 11054  62.4872 198.8383 0057450 192.9653 166.8284  1.93504662169140\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11054     1440.0000    -25388.34079710   -3403.40500597   -9531.93443534\n                           -0.90793207      -2.01537981       3.10310036\n11054        0.0000    -25891.75008739   -8827.17634454      -7.11543233\n                            0.56259784      -1.66520502       3.37653764\n11054      360.0000     25891.99345977    7409.73868823    2577.67984854\n                           -0.18479992       1.80406882      -3.39491593\n11054     1080.0000     25613.56652955    4649.87873299    7410.72787690\n                            0.56951792       1.98044940      -3.24876400\n11054      720.0000    -26175.21689483   -6242.60712582   -4868.83227692\n                           -0.17573206      -1.87873379       3.30745510\n11054      719.0000    -26163.75460152   -6129.66517687   -5067.10706616\n                           -0.20634035      -1.88596920       3.30164344\n11054    -2880.0000    -14933.48469609  -14743.73993336   17504.98769057\n                            3.00456330      -0.25365150       2.32952622\n\n1 11057U 78095A   02077.63138055  .00000149  00000-0  10000-3 0  5309\n2 11057  63.7422 203.7241 6717022 282.5815  14.8175  2.34176921180342\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n11057     1440.0000      7487.87611216  -15287.29467029   34493.07590041\n                            1.86179494       0.47483499       0.62605389\n11057        0.0000    -10507.51193223   -4611.31451017       0.19783099\n                           -2.18140918      -4.00411475       5.66127724\n11057      360.0000     20970.88101088   -7510.36265833   31022.77017800\n                            0.99148126       1.17155611      -1.36986859\n11057     1080.0000     23275.29351376     644.95594684   17692.25539266\n                           -0.47864173       1.36230389      -2.92144014\n11057      720.0000     -4731.80410387  -15608.68113270   25160.74838285\n                            1.90090167      -0.49586326       2.46683975\n11057      719.0000     -4845.71559110  -15578.51400495   25012.06196398\n                            1.89663033      -0.50977445       2.48923053\n11057    -2880.0000      5859.91793323  -15633.31281630   33804.16988953\n                            1.90326131       0.38485257       0.85574795\n\n1 25989U 99066A   02080.37500000  .00000000  00000-0  00000+0 0  4082\n2 25989  34.6613 172.6861 7995138 107.4591   3.7150  0.50103982  1159\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n25989     1440.0000    -25416.31130478   97972.48561814  -65060.88827233\n                           -0.78954842      -0.14306696       0.17001526\n25989        0.0000     13446.88594396   -7340.90106369    3841.96143867\n                            5.25343486       3.15492823      -2.63964361\n25989      360.0000     27779.82527478   52562.51176863  -38630.70190521\n                           -0.55664353       1.86219779      -1.23020626\n25989     1080.0000     -7306.20327748   95752.02176493  -65174.16312805\n                           -0.87298261       0.35585797      -0.16572419\n25989      720.0000     11590.33887760   81938.35349884  -57377.90111303\n                           -0.85404887       0.94997888      -0.57613966\n25989      719.0000     11641.57460814   81881.29555505  -57343.27701803\n                           -0.85377652       0.95189906      -0.57748483\n25989    -2880.0000     11272.09487443   -8515.50280915    4864.79909744\n                            5.79043510       2.81320525      -2.44112033\n\n1 25990U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\n2 25990  44.2277 151.3168 8216838 138.1402 356.4324  0.55308221  1445\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n25990     1440.0000    -52618.00493523   87304.59689650  -50015.63309567\n                           -0.57621934      -0.27322941       0.50620611\n25990        0.0000     -7091.02772480   -7636.71306513    9880.83142057\n                            6.57104133      -2.04322973      -1.34121281\n25990      360.0000      6941.12895930   47572.57987853  -44038.73006094\n                           -1.08656024       1.87029231      -1.09113296\n25990     1080.0000    -37147.20354816   87628.63341664  -57597.53860234\n                           -0.84368212       0.24906550       0.18441609\n25990      720.0000    -16647.88296791   75877.09428533  -57189.29131723\n                           -1.04294149       0.86860516      -0.25294533\n25990      719.0000    -16585.29849772   75824.91738346  -57174.06802345\n                           -1.04338459       0.87062791      -0.25447089\n25990    -2880.0000    -51622.41035047   33904.99902952   -4876.35838422\n                            1.10240406      -1.97705598       1.17712115\n\n1 95990U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\n2 95990  00.0000 151.3168 8216838 138.1402 356.4324  0.55308221  1445\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n95990     1440.0000    -43028.16265150  105027.52135728     -86.44476862\n                           -0.67725863      -0.45493814       0.00148717\n95990        0.0000     -9276.06994652  -11192.55362548      19.74681299\n                            6.77723696      -1.59630165      -0.00494890\n95990      360.0000     15617.58408603   63138.16468711     -83.51392335\n                           -0.87578308       2.26526301      -0.00149824\n95990     1080.0000    -26002.93639782  108081.44788857    -104.30468723\n                           -0.88306083       0.18258216       0.00083816\n95990      720.0000     -5490.19722222   96188.86013527    -105.41918111\n                           -0.99754948       0.95972033       0.00001050\n95990      719.0000     -5430.31436335   96131.20728971    -105.40170659\n                           -0.99769520       0.96229431       0.00000772\n95990    -2880.0000    -50722.91908053   35425.12742795       6.30125070\n                            0.87609338      -2.39985300       0.00042810\n\n1 95991U 99066B   02082.00000000  .00000000  00000-0  00000+0 0  3260\n2 95991  90.0000 151.3168 8216838 138.1402 356.4324  0.55308221  1445\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n95991     1440.0000    -77142.85325007   42202.12222005  -71827.26078425\n                           -0.32460531       0.18262205       0.72425332\n95991        0.0000     -1449.02321392     866.63307741   13838.48853373\n                            5.94468426      -3.28999887      -2.15892181\n95991      360.0000    -14921.77469094    7923.57455850  -63270.62053583\n                           -1.61750897       0.88474033      -1.55440657\n95991     1080.0000    -65469.72451903   35711.05750889  -82650.53346374\n                           -0.74960338       0.41480084       0.26296217\n95991      720.0000    -44860.22328081   24346.42005898  -82042.76539914\n                           -1.16282667       0.63970290      -0.36210062\n95991      719.0000    -44790.44228688   24308.02618737  -82020.95956739\n                           -1.16401610       0.64034860      -0.36427748\n95991    -2880.0000    -53712.53773042   29617.54568095   -6866.35842176\n                            1.68961372      -0.92423244       1.68808926\n\n1 91054U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\n2 91054  00.0000 198.8383 0057450 192.9653 166.8284  1.93504662169140\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n91054     1440.0000    -27251.20268469    2043.78342172       0.38495984\n                           -0.29786110      -3.79880308       0.00056321\n91054        0.0000    -25895.34937682   -8812.84472596       1.89447853\n                            1.22184076      -3.60538998       0.00043874\n91054      360.0000     26402.04813901    5902.77390263      -1.53149539\n                           -0.84680670       3.75471305      -0.00048255\n91054     1080.0000     27064.51621164     386.20845725      -0.76370949\n                           -0.06617333       3.84655206      -0.00054573\n91054      720.0000    -27124.61157219   -3454.74894822       1.21377642\n                            0.47213367      -3.77884190       0.00051050\n91054      719.0000    -27151.98713483   -3227.90076505       1.18311173\n                            0.44038313      -3.78275109       0.00051191\n91054    -2880.0000    -11579.82311401  -24757.46950263       3.18227069\n                            3.45616853      -1.60319653       0.00008863\n\n1 91055U 78093A   02069.41465182 -.00000030  00000-0  00000+0 0  7661\n2 91055  90.0000 198.8383 0057450 192.9653 166.8284  1.93504662169140\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n91055     1440.0000    -23774.39057753   -8118.55368062  -10765.98615008\n                           -1.43090377      -0.48768145       3.49704597\n91055        0.0000    -25888.60922614   -8837.19928921     -13.17971782\n                           -0.00559055      -0.00121123       3.80658803\n91055      360.0000     25455.19401338    8689.98537421    2911.95169589\n                            0.38654168       0.13120863      -3.82717489\n91055     1080.0000     24359.72673396    8317.58804582    8368.10574508\n                            1.11669867       0.38041827      -3.66183508\n91055      720.0000    -25350.66900120   -8655.03112525   -5502.22898025\n                           -0.73280552      -0.24939182       3.72828528\n91055      719.0000    -25305.81182186   -8639.76373862   -5725.73068288\n                           -0.76242834      -0.25950543       3.72171824\n91055    -2880.0000    -17870.58154378   -6096.60286646   19755.09881530\n                            2.61518678       0.89286753       2.62338324\n\n1 93581U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\n2 93581   0.0000  93.7945 0005741 214.4722 151.5103  1.00270260 23672\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n93581     1440.0000     -7872.43690338   41444.98000544       1.73095095\n                           -3.01930947      -0.57268874       0.00082631\n93581        0.0000     -7161.07115751   41573.60354164       1.47785784\n                           -3.02869742      -0.52084436       0.00099181\n93581      360.0000    -41544.22550350   -7273.12667506      12.99813118\n                            0.53160320      -3.02755079      -0.00011053\n93581     1080.0000     41442.56532942    7708.77148721     -11.85036610\n                           -0.56090269       3.02393927       0.00012095\n93581      720.0000      7464.92402276  -41476.96198751      -1.57677016\n                            3.02746245       0.54571529      -0.00090532\n93581      719.0000      7283.20850156  -41509.30663531      -1.52257255\n                            3.02981868       0.53245704      -0.00090590\n93581    -2880.0000     -5732.21333730   41794.16566212       1.56528295\n                           -3.04480278      -0.41670821       0.00139395\n\n1 93582U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\n2 93582  90.0000  93.7945 0005741 214.4722 151.5103  1.00270260 23672\nDeep-Space type Ephemeris (SDP4) selected:\nEphem:SDP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n93582     1440.0000     -2770.54805468   41776.11141003    5173.24086584\n                            0.02451285      -0.37579865       3.04996048\n93582        0.0000     -2776.44858404   41857.32053783    4464.56890610\n                            0.02121236      -0.32425422       3.05590006\n93582      360.0000       299.47358533   -4581.23336275   41918.44819986\n                            0.20224452      -3.04912682      -0.33617946\n93582     1080.0000      -327.46314733    5015.63189813  -41860.76379322\n                           -0.20204370       3.04638074       0.36506868\n93582      720.0000      2771.04009467  -41779.25011765   -4798.74748702\n                           -0.02285229       0.34979589      -3.05608468\n93582      719.0000      2772.38478802  -41799.83758401   -4615.33487681\n                           -0.02196673       0.33644423      -3.05758868\n93582    -2880.0000     -2784.64498244   41983.35866717    3050.79000694\n                            0.01439633      -0.22144006       3.06511691\n\n1 94156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\n2 94156  00.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n94156     1440.0000      5611.69171535    4037.13827498      -0.00000000\n                           -4.50730682       6.21223117       0.00000000\n94156        0.0000     -6804.97205785    2139.79607495       0.00000000\n                           -2.35704590      -7.05634698       0.00000000\n94156      360.0000      5349.62740749    4374.60192400      -0.00000000\n                           -4.86600327       5.93916554       0.00000000\n94156     1080.0000     -6659.26209827    2501.79806026       0.00000000\n                           -2.75655222      -6.93228196       0.00000000\n94156      720.0000       793.05328945   -7099.56293484       0.00000000\n                            7.36795516       0.94789425      -0.00000000\n94156      719.0000       349.69283690   -7142.42833287       0.00000000\n                            7.40546107       0.48068087      -0.00000000\n94156    -2880.0000      4272.35249129    5450.08922859      -0.00000000\n                           -5.98845596       4.78095723      -0.00000000\n\n1 94156U 94029HF  02079.22959898  .00078684  00000-0  11005-1 0  3773\n2 94156  90.0000 162.4355 0207818 237.7291 120.3519 14.63437792297579\nNear-Earth type Ephemeris (SGP4) selected:\nEphem:SGP4   Tsince         X/Xdot           Y/Ydot           Z/Zdot\n94156     1440.0000      4836.62295892   -1530.96953882   -4710.41747366\n                           -4.98875992       1.57912650      -5.59417055\n94156        0.0000     -6806.87343390    2154.62647604      -1.48232172\n                           -0.11981850       0.03792697       7.43187690\n94156      360.0000      3851.26201663   -1219.06646095   -5619.40500022\n                           -5.93318266       1.87807113      -4.46693858\n94156     1080.0000     -6663.81947089    2109.34462099   -1362.72069332\n                           -1.48665647       0.47058160       7.28686744\n94156      720.0000      2184.42260844    -691.45031602    6762.52952941\n                            6.66557554      -2.10990048      -2.49518094\n94156      719.0000      1780.47096881    -563.58472454    6898.86094376\n                            6.79505660      -2.15088601      -2.04785706\n94156    -2880.0000       -52.48990110      16.61498950   -6935.32865533\n                           -7.28529619       2.30606492      -0.00738959\n\n"
  },
  {
    "path": "test3.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  test3.cpp      8 October 2002\n *\n *  A skeleton main() function to test the speed of SxPx\n *  functions,  and the positional differences between SGPx and SDPx.\n *  (I was curious as to whether the extra code in SDPx made any really\n *  significant difference;  it was starting to look as if SDPx was a\n *  waste of code.  Turns out that it depends greatly on the satellite\n *  in question;  I'll post results in a bit,  when I find the time.)\n *\n *  Also,  this demonstrates how to use the new dynamically-loaded\n *  functions in 'dynamic.cpp'.  This way,  if your program can't\n *  find 'sat_code.dll',  it'll gracefully give you a message about it.\n *  In the case of my own _Guide_ planetarium software,  if it can't\n *  find the DLL,  it can fall back on its own SGP4-only code.\n */\n\n#include <stdio.h>\n#include <math.h>\n#include <string.h>\n#include <stdlib.h>\n#include <time.h>\n#include \"norad.h\"\n\n/* Main program */\nint main( int argc, char **argv)\n{\n   FILE *ifile = fopen( (argc == 1) ? \"test.tle\" : argv[1], \"rb\");\n   tle_t tle; /* Pointer to two-line elements set for satellite */\n   char line1[100], line2[100];\n   double t_since = atof( argv[2]);\n   double rms_diff = 0.;\n   int verbose = 0, n_found = 0, n_runs = 1, show_sdp = 1;\n   int ephem = 1;       /* default to SGP4 */\n   int i, j;            /* Index for loops etc */\n   clock_t t0;\n\n   if( !ifile)\n      {\n      printf( \"Couldn't open input TLE file\\n\");\n      exit( -1);\n      }\n\n   for( i = 3; i < argc; i++)\n      if( argv[i][0] == '-')\n         switch( argv[i][1])\n            {\n            case 'v':\n               verbose = atoi( argv[i] + 2);\n               break;\n            case 'n':\n               n_runs = atoi( argv[i] + 2);\n               break;\n            case 's':\n               show_sdp = atoi( argv[i] + 2);\n               break;\n            default:\n               printf( \"Option '%s' ignored\\n\", argv[i]);\n               break;\n            }\n\n   fgets( line1, sizeof( line1), ifile);\n   t0 = clock( );\n   while( fgets( line2, sizeof( line2), ifile))\n      {\n      if( line1[0] == '1' && line2[0] == '2' &&\n               parse_elements( line1, line2, &tle) >= 0)  /* hey! we got a TLE! */\n         if( select_ephemeris( &tle) && show_sdp < 2)\n            {\n            double sgp_sat_params[N_SAT_PARAMS];\n            double sdp_sat_params[N_SAT_PARAMS];\n            double perigee;\n\n#ifdef STATIC_LINK\n            SGP4_init( sgp_sat_params, &tle);\n            if( show_sdp)\n               SDP4_init( sdp_sat_params, &tle);\n#else\n            if( SXPX_init( sgp_sat_params, &tle, 1))\n               {\n               printf( \"Couldn't load 'sat_code.dll'\\n\");\n               exit( -1);\n               }\n            if( show_sdp)\n               SXPX_init( sdp_sat_params, &tle, 3);\n#endif\n            perigee = sgp_sat_params[9] * (1. - tle.eo) - 1.;\n            line2[63] = '\\0';\n            if( verbose)\n               printf( \"%5ld %s\", (long)( perigee * 6378.14), line2);\n            for( j = 0; j <= n_runs; j++)\n               {\n               double posn2[3], pos[3], vel[3];\n               double delta, d2 = 0.;\n\n#ifdef STATIC_LINK\n               SGP4( t_since * (double)j, &tle, sgp_sat_params, posn2, vel);\n               if( show_sdp)\n                  SDP4( t_since * (double)j, &tle, sdp_sat_params, pos, vel);\n#else\n               SXPX( t_since * (double)j, &tle, sgp_sat_params, posn2, vel, 1);\n               if( show_sdp)\n                  SXPX( t_since * (double)j, &tle, sdp_sat_params, pos, vel, 3);\n#endif\n               if( !show_sdp)\n                  memcpy( pos, posn2, 3 * sizeof( double));\n\n               for( i = 0; i < 3; i++)\n                  {\n                  delta = pos[i] - posn2[i];\n                  d2 += delta * delta;\n                  }\n               if( j == 1)\n                  rms_diff += d2;\n               if( verbose)\n                  printf( \"%8.1lf%s\", sqrt( d2), (j == n_runs ? \"\\n\" : \"\"));\n               }\n            n_found++;\n            }\n      strcpy( line1, line2);\n      }\n   fclose( ifile);\n   t0 = clock( ) - t0;\n   printf( \"%d deep-space objects found\\n\", n_found);\n   printf( \"%lf seconds elapsed\\n\", (double)t0 / (double)CLOCKS_PER_SEC);\n   if( n_found)\n      printf( \"RMS difference = %.1lf\\n\", sqrt( rms_diff / (double)n_found));\n   return(0);\n} /* End of main() */\n\n/*------------------------------------------------------------------*/\n"
  },
  {
    "path": "test_des.cpp",
    "content": "/* Copyright (C) 2021, Project Pluto.  See LICENSE.  */\n/* Code to test Alpha-5 and 'extended' Alpha-5 (Super-5) designation\nschemes.  These enable us to have nine-digit NORAD numbers\nwhile still fitting (most of the) requirements of the current TLE\nformat.  See comments about the Alpha-5 and Super-5 schemes in\n'get_el.cpp'.\n\nRun without command-line arguments,  this tries out all possible\nSuper-5 designations with a \"round-trip\" test : encode,  then\ndecode to ensure we get the original number.  A couple past\n34^5 are run to make sure that they fail as expected.  In this\nscheme,  the lexical order of Super-5 designations should\nmismatch the numerical order at several points,  and this test\ncode verifies that. */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#include <math.h>\n#include \"norad.h\"\n\nint main( const int argc, const char **argv)\n{\n   char buff[160], *line1 = buff, line2[80];\n   int n;\n   tle_t tle;\n   const int one_billion = 1000000000;\n\n   strcpy( line1,\n      \"1 00005U 58002B   21124.86416743 -.00000116  00000-0 -00000-0 0  9998\");\n   strcpy( line2,\n      \"2 00005  34.2496 318.0626 1847159 358.7427   0.8906 10.84838792240168\");\n   parse_elements( line1, line2, &tle);\n   if( argc > 1)\n      {\n      n = tle.norad_number = atoi( argv[1]);\n      write_elements_in_tle_format( buff, &tle);\n      printf( \"Super-5 version : '%.5s'\\n\", buff + 2);\n      parse_elements( line1, line2, &tle);\n      printf( \"Got back NORAD %d\\n\", tle.norad_number);\n      assert( n == tle.norad_number);\n      return( 0);\n      }\n\n   printf( \"Testing all Super-5 designations.  This should take an hour or two.\\n\");\n   for( n = 0; n < one_billion + 2; n++)\n      {\n      tle.norad_number = n;\n      write_elements_in_tle_format( buff, &tle);\n      parse_elements( line1, line2, &tle);\n      if( tle.norad_number != n)\n         printf( \"Got back NORAD %d (should be %d)\\n%s\\n\",\n                     tle.norad_number, n, buff);\n      if( !(n % 1000000))        /* progress indicator */\n         {\n         printf( \".\");\n         fflush( stdout);\n         }\n      }\n   return( 0);\n}\n"
  },
  {
    "path": "test_out.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <string.h>\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#define PI \\\n   3.1415926535897932384626433832795028841971693993751058209749445923\n\nvoid greg_day_to_dmy( const long jd, int *day,\n                  int *month, long *year)\n{\n   const long mar_1_year_0 = 1721120L;     /* JD 1721120 = 1.5 Mar 0 Greg */\n   const long one_year = 365L;\n   const long four_years = 4 * one_year + 1;\n   const long century = 25 * four_years - 1L;  /* days in 100 'normal' yrs */\n   const long quad_cent = century * 4 + 1;     /* days in 400 years */\n   long days = jd - mar_1_year_0;\n   long day_in_cycle = days % quad_cent;\n\n   if( day_in_cycle < 0)\n      day_in_cycle += quad_cent;\n   *year = ((days - day_in_cycle) / quad_cent) * 400L;\n   *year += (day_in_cycle / century) * 100L;\n   if( day_in_cycle == quad_cent - 1)    /* extra leap day every 400 years */\n      {\n      *month = 2;\n      *day = 29;\n      return;\n      }\n   day_in_cycle %= century;\n   *year += (day_in_cycle / four_years) * 4L;\n   day_in_cycle %= four_years;\n   *year +=  day_in_cycle / one_year;\n   if( day_in_cycle == four_years - 1)    /* extra leap day every 4 years */\n      {\n      *month = 2;\n      *day = 29;\n      return;\n      }\n   day_in_cycle %= one_year;\n   *month = 5 * (day_in_cycle / 153L);\n   day_in_cycle %= 153L;\n   *month += 2 * (day_in_cycle / 61L);\n   day_in_cycle %= 61L;\n   if( day_in_cycle >= 31)\n      {\n      (*month)++;\n      day_in_cycle -= 31;\n      }\n   *month += 3;\n   *day = day_in_cycle + 1;\n   if( *month > 12)\n      {\n      *month -= 12;\n      (*year)++;\n      }\n}\n\n/* Example code to add BSTAR data using Ted Molczan's method.  It just\n   reads in TLEs,  computes BSTAR if possible,  then writes out the\n   resulting modified TLE.\n\n   Add the '-v' (verbose) switch,  and it also writes out the orbital\n   period and perigee/apogee distances.  Eventually,  I'll probably\n   set it up to dump other data that are not immediately obvious\n   just by looking at the TLEs... */\n\nint main( const int argc, const char **argv)\n{\n   FILE *ifile;\n   const char *filename;\n   char line0[100], line1[100], line2[100];\n   int i, verbose = 0;\n   const char *norad = NULL, *intl = NULL;\n   int legend_shown = 0;\n\n   for( i = 2; i < argc; i++)\n      if( argv[i][0] == '-')\n         switch( argv[i][1])\n            {\n            case 'v':\n               verbose = 1;\n               break;\n            case 'n':\n               norad = argv[i] + 2;\n               if( !*norad && i < argc - 1)\n                  norad = argv[++i];\n               printf( \"Looking for NORAD %s\\n\", norad);\n               break;\n            case 'i':\n               intl = argv[i] + 2;\n               if( !*intl && i < argc - 1)\n                  intl = argv[++i];\n               printf( \"Looking for international ID %s\\n\", intl);\n               break;\n            default:\n               printf( \"'%s': unrecognized option\\n\", argv[i]);\n               return( -1);\n               break;\n            }\n   filename = (argc == 1 ? \"all_tle.txt\" : argv[1]);\n   ifile = fopen( filename, \"rb\");\n   if( !ifile)\n      {\n      fprintf( stderr, \"Couldn't open '%s': \", filename);\n      perror( \"\");\n      return( -1);\n      }\n   *line0 = *line1 = '\\0';\n   while( fgets( line2, sizeof( line2), ifile))\n      {\n      if( *line1 == '1' && (!norad || !memcmp( line1 + 2, norad, 5))\n                        && (!intl || !memcmp( line1 + 9, intl, strlen( intl)))\n                   && *line2 == '2')\n         {\n         tle_t tle;\n         const int err_code = parse_elements( line1, line2, &tle);\n\n         if( err_code >= 0)\n            {\n            char obuff[200];\n            double params[N_SGP4_PARAMS], c2;\n\n            if( verbose && !legend_shown)\n               {\n               legend_shown = 1;\n               printf(\n  \"1 NoradU COSPAR   Epoch.epoch     dn/dt/2  d2n/dt2/6 BSTAR    T El# C\\n\"\n  \"2 NoradU Inclina RAAscNode Eccent  ArgPeri MeanAno  MeanMotion Rev# C\\n\");\n\n               }\n            SGP4_init( params, &tle);\n            c2 = params[0];\n            if( c2 && tle.xno)\n               tle.bstar = tle.xndt2o / (tle.xno * c2 * 1.5);\n            write_elements_in_tle_format( obuff, &tle);\n            if( strlen( line0) < 60)\n               printf( \"%s\", line0);\n            printf( \"%s\", obuff);\n            if( verbose)\n               {\n               const double a1 = pow(xke / tle.xno, two_thirds);  /* in Earth radii */\n               long year, ijd;\n               int month, day;\n               double frac;\n\n               printf( \"Inclination: %8.4f     \", tle.xincl * 180. / PI);\n               printf( \"   Perigee: %.4f km\\n\",\n                    (a1 * (1. - tle.eo) - 1.) * earth_radius_in_km);\n\n               printf( \"Asc node:    %8.4f     \", tle.xnodeo * 180. / PI);\n               printf( \"   Apogee: %.4f km\\n\",\n                    (a1 * (1. + tle.eo) - 1.) * earth_radius_in_km);\n\n               printf( \"Arg perigee: %8.4f     \", tle.omegao * 180. / PI);\n               printf( \"   Orbital period: %.4f min\\n\",\n                    2. * pi / tle.xno);\n\n               printf( \"Mean anom:   %8.4f     \", tle.xmo * 180. / PI);\n               ijd = (long)( tle.epoch + 0.5);\n               frac = tle.epoch + 0.5 - ijd;\n               greg_day_to_dmy( ijd, &day, &month, &year);\n               printf( \"   Epoch: JD %.5f = %ld-%02d-%02d.%05d\\n\", tle.epoch,\n                              year, month, day, (int)( frac * 100000.));\n               printf( \"NORAD number %7d         \", tle.norad_number);\n               printf( \"Semimajor axis : %4f km\\n\", a1 * earth_radius_in_km);\n               }\n            if( err_code)\n               printf( \"Checksum error %d\\n\", err_code);\n            }\n         }\n      strcpy( line0, line1);\n      strcpy( line1, line2);\n      }\n   fclose( ifile);\n   return( 0);\n}\n"
  },
  {
    "path": "test_sat.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  main.c          April 10  2001\n *\n *  A skeleton main() function to demonstrate the use of\n *  the various NORAD ephimerides in the norad.c file.\n *  The TLE sets used are those provided in NORAD's spacetrack\n *  report #3 so that a comparison with their own results\n *  can be made. The results produced by these software agree\n *  with NORAD's to the 5th or 6th figure, whatever this means!\n *  (But please note that NORAD uses mostly 'float' types with\n *  only a few 'doubles' for the critical variables).\n */\n\n#include <stdio.h>\n#include <string.h>\n#include \"norad.h\"\n\n#define INTENTIONALLY_UNUSED_PARAMETER( param) (void)(param)\n\nstatic double test_data[5 * 6 * 5] = {\n\n                        /* SGP: */\n           2328.96594238,  -5995.21600342,   1719.97894287,\n                       2.91110113,     -0.98164053,      -7.09049922,\n           2456.00610352,  -6071.94232177,   1222.95977784,\n                       2.67852119,     -0.44705850,      -7.22800565,\n           2567.39477539,  -6112.49725342,    713.97710419,\n                       2.43952477,      0.09884824,      -7.31899641,\n           2663.03179932,  -6115.37414551,    195.73919105,\n                       2.19531813,      0.65333930,      -7.36169147,\n           2742.85470581,  -6079.13580322,   -328.86091614,\n                       1.94707947,      1.21346101,      -7.35499924,\n\n                        /* SGP4: */\n           2328.97048951,  -5995.22076416,   1719.97067261,\n                       2.91207230,     -0.98341546,      -7.09081703,\n           2456.10705566,  -6071.93853760,   1222.89727783,\n                       2.67938992,     -0.44829041,      -7.22879231,\n           2567.56195068,  -6112.50384522,    713.96397400,\n                       2.44024599,      0.09810869,      -7.31995916,\n           2663.09078980,  -6115.48229980,    196.39640427,\n                       2.19611958,      0.65241995,      -7.36282432,\n           2742.55133057,  -6079.67144775,   -326.38095856,\n                       1.94850229,      1.21106251,      -7.35619372,\n\n                        /* SGP8: */\n           2328.87265015,  -5995.21289063,   1720.04884338,\n                       2.91210661,     -0.98353850,      -7.09081554,\n           2456.04577637,  -6071.90490722,   1222.84086609,\n                       2.67936245,     -0.44820847,      -7.22888553,\n           2567.68383789,  -6112.40881348,    713.29282379,\n                       2.43992555,      0.09893919,      -7.32018769,\n           2663.49508667,  -6115.18182373,    194.62816810,\n                       2.19525236,      0.65453661,      -7.36308974,\n           2743.29238892,  -6078.90783691,   -329.73434067,\n                       1.94680957,      1.21500109,      -7.35625595,\n\n                        /* SDP4: */\n           7473.37066650,    428.95261765,   5828.74786377,\n                       5.10715413,      6.44468284,      -0.18613096,\n          -3305.22537232,  32410.86328125, -24697.1767581,\n                      -1.30113538,     -1.15131518,      -0.28333528,\n          14271.28759766,  24110.46411133,  -4725.76837158,\n                      -0.32050445,      2.67984074,      -2.08405289,\n          -9990.05883789,  22717.35522461, -23616.89062501,\n                      -1.01667246,     -2.29026759,       0.72892364,\n           9787.86975097,  33753.34667969, -15030.81176758,\n                      -1.09425066,      0.92358845,      -1.52230928,\n\n                        /* SDP8: (gotta fix) */\n           7469.47631836,    415.99390792,   5829.64318848,\n                       5.11402285,      6.44403201,      -0.18296110,\n          -3337.38992310,  32351.39086914, -24658.63037109,\n                      -1.30200730,     -1.15603013,      -0.28164955,\n          14226.54333496,  24236.08740234,  -4856.19744873,\n                      -0.33951668,      2.65315416,      -2.08114153,\n         -10151.59838867,  22223.69848633, -23392.39770508,\n                      -1.00112480,     -2.33532837,       0.76987664,\n           9420.08203125,  33847.21875000, -15391.06469727,\n                      -1.11986055,      0.85410149,      -1.49506933 };\n\n/* Main program */\nint main( const int argc, const char **unused_argv)\n{\n  double vel[3], pos[3]; /* Satellite position and velocity vectors */\n  double *test_ptr = test_data;\n\n  tle_t tle; /* Pointer to two-line elements set for satellite */\n\n  /* Data for the prediction type and time period */\n  double   ts = 0.;    /* Time since TLE epoch to start predictions */\n  double   tf = 1440.; /* Time over which predictions are required  */\n  double delt = 360.;  /* Time interval between predictions         */\n\n  double tsince; /* Time since epoch (in minutes) */\n\n  int i; /* Index for loops etc */\n  const char *tle_data[16] = {\n      \"1 88888U          80275.98708465  .00073094  13844-3  66816-4 0    87\",\n      \"2 88888  72.8435 115.9689 0086731  52.6988 110.5714 16.05824518  1058\",\n      \"1 11801U          80230.29629788  .01431103  00000-0  14311-1       2\",\n      \"2 11801U 46.7916 230.4354 7318036  47.4722  10.4117  2.28537848     2\",\n          /* GOES 9 */\n      \"1 23581U 95025A   01311.43599209 -.00000094  00000-0  00000+0 0  8214\",\n      \"2 23581   1.1236  93.7945 0005741 214.4722 151.5103  1.00270260 23672\",\n          /* Cosmos 1191 */\n      \"1 11871U 80057A   01309.36911127 -.00000499 +00000-0 +10000-3 0 08380\",\n      \"2 11871 067.5731 001.8936 6344778 181.9632 173.2224 02.00993562062886\",\n          /* ESA-GEOS 1 */\n      \"1 09931U 77029A   01309.17453186 -.00000329 +00000-0 +10000-3 0 05967\",\n      \"2 09931 026.4846 264.1300 6609654 082.2734 342.9061 01.96179522175451\",\n          /* Cosmos 1217 */\n      \"1 12032U 80085A   01309.42683181  .00000182  00000-0  10000-3 0  3499\",\n      \"2 12032  65.2329  86.7607 7086222 172.0967 212.4632  2.00879501101699\",\n          /* Molniya 3-19Rk */\n      \"1 13446U 82083E   01283.10818257  .00098407  45745-7  54864-3 0  6240\",\n      \"2 13446  62.1717  83.8458 7498877 273.9677 320.2568  2.06357523137203\",\n          /* Ariane Deb */\n      \"1 23246U 91015G   01311.70347086  .00004957  00000-0  43218-2 0  8190\",\n      \"2 23246   7.1648 263.6949 5661268 241.8299  50.5793  4.44333001129208\" };\n\n   INTENTIONALLY_UNUSED_PARAMETER( unused_argv);\n   for( i = 1; i <= 17; i++)  /* Loop for each type of ephemeris */\n      {\n      int tle_idx = ((i - 2) / 2) * 2, err_code;\n      int ephem, is_deep;\n      const char *ephem_names[6] = { NULL, \"SGP \", \"SGP4\", \"SGP8\", \"SDP4\", \"SDP8\" };\n      double sat_params[N_SAT_PARAMS];\n\n      /* Select the sgp or sdp TLE set for use below */\n      if( tle_idx < 0) tle_idx = 0;\n      printf( \"\\n%s\\n%s\", tle_data[tle_idx], tle_data[tle_idx + 1]);\n      err_code = parse_elements( tle_data[tle_idx], tle_data[tle_idx + 1], &tle);\n      if( err_code)\n         printf( \"\\nError parsing elements: %d\\n\", err_code);\n\n      if( i <= 5)\n         ephem = i;\n      else\n         ephem = 4 + ((i - 5) % 2);\n\n      /* Select ephemeris type */\n      /* Will select a \"deep\" (SDPx) or \"general\" (SGPx) ephemeris  */\n      /* depending on the TLE parameters of the satellite:          */\n      is_deep = select_ephemeris( &tle);\n\n/*    printf( \"BStar: %.8lf\\n\", tle.bstar);  */\n      if( is_deep)\n         printf(\"\\nDeep-Space type Ephemeris (SDP*) selected:\");\n      else\n         printf(\"\\nNear-Earth type Ephemeris (SGP*) selected:\");\n\n      /* Print some titles for the results */\n      printf(\"\\nEphem:%s   Tsince         \"\n             \"X/Xdot           Y/Ydot           Z/Zdot\\n\", ephem_names[ephem]);\n\n      /* Calling of NORAD routines */\n      /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8)   */\n      /* will be called in turn with the appropriate TLE set */\n      switch( ephem)\n         {\n         case 1:\n            SGP_init( sat_params, &tle);\n            break;\n         case 2:\n            SGP4_init( sat_params, &tle);\n            break;\n         case 3:\n            SGP8_init( sat_params, &tle);\n            break;\n         case 4:\n            SDP4_init( sat_params, &tle);\n            break;\n         case 5:\n            SDP8_init( sat_params, &tle);\n            break;\n         }\n\n      for( tsince = ts; tsince <= tf; tsince += delt)\n         {\n         switch( ephem)\n            {\n            case 1:\n               SGP(tsince, &tle, sat_params, pos, vel);\n               break;\n            case 2:\n               SGP4(tsince, &tle, sat_params, pos, vel);\n               break;\n            case 3:\n               SGP8(tsince, &tle, sat_params, pos, vel);\n               break;\n            case 4:\n               SDP4(tsince, &tle, sat_params, pos, vel);\n               break;\n            case 5:\n               SDP8(tsince, &tle, sat_params, pos, vel);\n               break;\n            }\n\n         /* Calculate and print results */\n         vel[0] /= 60.;    /* cvt km/minute to km/second */\n         vel[1] /= 60.;\n         vel[2] /= 60.;\n\n         if( argc > 1 && i <= 5)   /* wanna show _difference from test data_ */\n            {\n            pos[0] -= *test_ptr++;\n            pos[1] -= *test_ptr++;\n            pos[2] -= *test_ptr++;\n            vel[0] -= *test_ptr++;\n            vel[1] -= *test_ptr++;\n            vel[2] -= *test_ptr++;\n            }\n         printf(\"       %12.4f   %16.8f %16.8f %16.8f \\n\",\n                            tsince,pos[0],pos[1],pos[2]);\n         printf(\"                      %16.8f %16.8f %16.8f \\n\",\n                                   vel[0],vel[1],vel[2]);\n\n         } /* End of for(tsince = ts; tsince <= tf; tsince += delt) */\n      } /* End of for (i=1; i<=17; i++) */\n\n  return(0);\n} /* End of main() */\n\n/*------------------------------------------------------------------*/\n"
  },
  {
    "path": "tle2mpc.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#include \"watdefs.h\"\n#include \"norad.h\"\n#include \"afuncs.h\"\n\n/* Code to read TLEs of the sort I post on github.com/Bill-Gray/tles,\nwhere there's one TLE per day,  and output ephemerides of the sort\nthe Minor Planet Center wants for their Distant Artsat Observation\nPage (DASO),  at https://www.minorplanetcenter.net/iau/artsats/artsats.html.\nFor DASO,  we need geocentric ephems with positions only,  in equatorial\nJ2000,  in AU,  at 0.1-day intervals.  So each time we read a TLE,  ten\npositions are output.         */\n\nstatic void error_exit( void)\n{\n   fprintf( stderr, \"'tle2mpc' needs the name of an input TLE file and an output\\n\");\n   fprintf( stderr, \"MPC ephemeris file.  See 'tle2mpc.cpp' for details.\\n\");\n   exit( -1);\n}\n\nstatic const char *err_fgets( char *buff, size_t buffsize, FILE *ifile)\n{\n   const char *rval = fgets( buff, (int)buffsize, ifile);\n\n   assert( rval);\n   return( rval);\n}\n\nint main( const int argc, const char **argv)\n{\n   char buff[200];\n   FILE *ifile, *ofile;\n   bool object_name_shown = false;\n\n   if( argc < 3)\n      error_exit( );\n   ifile = fopen( argv[1], \"rb\");\n   if( !ifile)\n      {\n      fprintf( stderr,  \"Couldn't open input file '%s'\\n\", argv[1]);\n      error_exit( );\n      }\n   ofile = fopen( argv[2], \"wb\");\n   if( !ofile)\n      {\n      fprintf( stderr,  \"Couldn't open output file '%s'\\n\", argv[2]);\n      error_exit( );\n      }\n   while( fgets( buff, sizeof( buff), ifile))\n      if( !memcmp( buff, \"# Ephem range:\", 14))\n         {\n         const double mjd1 = atof( buff + 15);\n         const double mjd2 = atof( buff + 28);\n\n         fprintf( ofile, \"%f 0.1 %d 0,1,1 \", 2400000.5 + mjd1,\n                           (int)( (mjd2 - mjd1) * 10. + .5));\n         }\n      else if( !memcmp( buff, \"# MJD \", 6))\n         {\n         double mjd = atof( buff + 6);\n         char line2[80];\n         tle_t tle;  /* Structure for two-line elements set for satellite */\n         bool is_a_tle;\n\n         err_fgets( buff, sizeof( buff), ifile);\n         if( !object_name_shown)\n            fprintf( ofile, \"%s\", buff);\n         object_name_shown = true;\n         err_fgets( buff, sizeof( buff), ifile);\n         err_fgets( line2, sizeof( line2), ifile);\n         is_a_tle = (parse_elements( buff, line2, &tle) >= 0);\n         assert( is_a_tle);\n         if( is_a_tle)\n            {\n            int i, j;\n            double sat_params[N_SAT_PARAMS], precess_matrix[9];\n            const bool is_deep = select_ephemeris( &tle);\n            const double year = 2000. + (mjd - 51545.) / 365.25;\n\n            if( is_deep)\n               SDP4_init( sat_params, &tle);\n            else\n               SGP4_init( sat_params, &tle);\n            setup_precession( precess_matrix, year, 2000.);\n            for( i = 0; i < 10; i++, mjd += 0.1)\n               {\n               double posn[3], j2000_posn[3];\n               const double t_since = (mjd - (tle.epoch - 2400000.5)) * minutes_per_day;\n\n               if( is_deep)\n                  SDP4( t_since, &tle, sat_params, posn, NULL);\n               else\n                  SGP4( t_since, &tle, sat_params, posn, NULL);\n               for( j = 0; j < 3; j++)\n                  posn[j] /= AU_IN_KM;\n               precess_vector( precess_matrix, posn, j2000_posn);\n               fprintf( ofile, \"%.5f%16.10f%16.10f%16.10f\\n\",\n                    mjd + 2400000.5, j2000_posn[0], j2000_posn[1], j2000_posn[2]);\n               }\n            }\n         }\n               /* Rewind to start of input & re-read,  looking for comments */\n   fseek( ifile, 0L, SEEK_SET);\n   while( fgets( buff, sizeof( buff), ifile))\n      if( !memcmp( buff, \"# Ephem range: \", 14))\n         while( fgets( buff, sizeof( buff), ifile) && memcmp( buff, \"# 1 NoradU\", 10))\n            fprintf( ofile, \"%s\", buff);\n\n   fclose( ifile);\n   fclose( ofile);\n   return( 0);\n}\n"
  },
  {
    "path": "tle_date.c",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <time.h>\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n#include \"watdefs.h\"\n#include \"afuncs.h\"\n#include \"date.h\"\n\n/* Code to extract TLEs for a particular date (MJD).  It looks\nthrough all TLEs listed in 'tle_list.txt' and outputs those matching\nthe MJD given on the command line.  */\n\nchar path[100];\nint verbose;\n\nstatic void extract_tle_for_date( const char *fname, const double mjd)\n{\n   char prev_line[100], buff[100];\n   FILE *ifile = fopen( fname, \"rb\");\n   double step = 0.;\n\n   assert( strlen( fname) < 20);\n   assert( strlen( path) < 60);\n   strcpy( buff, path);\n   strcat( buff, fname);\n   ifile = fopen( buff, \"rb\");\n   if( !ifile)\n      {\n      printf( \"'%s' not opened\\n\", fname);\n      exit( -1);\n      }\n   if( verbose)\n      printf( \"Looking at '%s' for %f\\n\", fname, mjd);\n   *prev_line = '\\0';\n   while( fgets( buff, sizeof( buff), ifile))\n      {\n      if( !memcmp( buff, \"# MJD \", 6) && atof( buff + 6) <= mjd\n                     && atof( buff + 6) + step > mjd)\n         {\n         size_t i;\n\n         printf( \"%s\", prev_line);    /* show 'worst residual' data */\n         for( i = 0; i < 3 && fgets( buff, sizeof( buff), ifile); i++)\n            printf( \"%s\", buff);   /* obj name,  lines 1 and 2 of TLE */\n         fclose( ifile);\n         return;\n         }\n      else if( !memcmp( buff, \"# Ephem range: \", 15))\n         {\n         double mjd1, mjd2;\n\n         if( sscanf( buff + 15, \"%lf %lf %lf\", &mjd1, &mjd2, &step) != 3)\n            {\n            printf( \"Ephem step problem in '%s'\\n'%s'\\n\",\n                     fname, buff);\n            exit( -2);\n            }\n         if( mjd < mjd1 || mjd > mjd2)\n            {\n            if( verbose)\n               printf( \"'%s': outside range\\n\", fname);\n            fclose( ifile);\n            return;\n            }\n         }\n      else if( !memcmp( buff, \"# Include \", 10))\n         {\n         int i = 0;\n         char *filename = buff + 10;\n\n         while( buff[i] >= ' ')\n            i++;\n         buff[i] = '\\0';\n         if( memcmp( filename, \"classfd\", 7) && memcmp( filename, \"inttles\", 7)\n               && memcmp( filename, \"all_tle\", 7)\n               && memcmp( filename, \"old_tle\", 7))\n            extract_tle_for_date( filename, mjd);\n         }\n      strcpy( prev_line, buff);\n      }\n   fclose( ifile);\n}\n\nstatic void err_exit( void)\n{\n   printf( \"tle_date (MJD)\\n\");\n   exit( -1);\n}\n\n#ifdef ON_LINE_VERSION\nint dummy_main( const int argc, const char **argv)\n#else\nint main( const int argc, const char **argv)\n#endif\n{\n   const double jan_1_1970 = 2440587.5;\n   const double curr_t = jan_1_1970 + (double)time( NULL) / seconds_per_day;\n   double mjd;\n\n   if( argc < 2)\n      err_exit( );\n   mjd = atof( argv[1]);\n   mjd = get_time_from_string( curr_t, argv[1], FULL_CTIME_YMD, NULL)\n                        - 2400000.5;\n   if( mjd < 40000 || mjd > 65000)\n      err_exit( );\n   if( argc > 2)\n      strcpy( path, argv[2]);\n   if( argc > 3)\n      verbose = 1;\n   extract_tle_for_date( \"tle_list.txt\", mjd);\n   return( 0);\n}\n\n#ifdef ON_LINE_VERSION\n#include <cgi_func.h>\n\nint main( void)\n{\n   const char *argv[20];\n   const size_t max_buff_size = 40000;\n   char *buff = (char *)malloc( max_buff_size);\n   char field[30], date_text[80];\n   FILE *lock_file = fopen( \"lock.txt\", \"w\");\n   extern char **environ;\n   int cgi_status;\n\n   avoid_runaway_process( 15);\n   printf( \"Content-type: text/html\\n\\n\");\n   printf( \"<html> <body> <pre>\\n\");\n   if( !lock_file)\n      {\n      printf( \"<p> Server is busy.  Try again in a minute or two. </p>\");\n      printf( \"<p> Your TLEs are very important to us! </p>\");\n      return( 0);\n      }\n   fprintf( lock_file, \"We're in\\n\");\n   for( size_t i = 0; environ[i]; i++)\n      fprintf( lock_file, \"%s\\n\", environ[i]);\n   cgi_status = initialize_cgi_reading( );\n   strcpy( date_text, \"now\");\n   fprintf( lock_file, \"CGI status %d\\n\", cgi_status);\n   if( cgi_status <= 0)\n      {\n      printf( \"<p> <b> CGI data reading failed : error %d </b>\", cgi_status);\n      printf( \"This isn't supposed to happen.</p>\\n\");\n      return( 0);\n      }\n   while( !get_cgi_data( field, buff, NULL, max_buff_size))\n      {\n      if( !strcmp( field, \"date\") && strlen( buff) < 70)\n         {\n         strncpy( date_text, buff, sizeof( date_text));\n         date_text[sizeof( date_text) - 1] = '\\0';\n         }\n      }\n   free( buff);\n   argv[0] = \"tle_date\";\n   argv[1] = date_text;\n   argv[2] = \"/home/projectp/public_html/tles/\";\n   argv[3] = NULL;\n   dummy_main( 3, argv);\n   printf( \"</pre> </body> </html>\");\n   fclose( lock_file);\n   return( 0);\n}\n#endif\n"
  },
  {
    "path": "tle_out.cpp",
    "content": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\n   tle_out.cpp: code to convert the in-memory artificial satellite\nelements into the \"standard\" TLE (Two-Line Element) form described at\n\nhttps://en.wikipedia.org/wiki/Two-line_elements       */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdint.h>\n#include <math.h>\n#include <assert.h>\n#include \"norad.h\"\n\n      /* Useful constants to define,  in case the value of PI or the number\n         of minutes in a day should change: */\n#define PI 3.141592653589793238462643383279502884197169399375105\n#define MINUTES_PER_DAY 1440.\n#define MINUTES_PER_DAY_SQUARED (MINUTES_PER_DAY * MINUTES_PER_DAY)\n#define MINUTES_PER_DAY_CUBED (MINUTES_PER_DAY_SQUARED * MINUTES_PER_DAY)\n\n#define AE 1.0\n#define J1900 (2451545.5 - 36525. - 1.)\n\nstatic int add_tle_checksum_data( char *buff)\n{\n   int count = 69, rval = 0;\n\n   if( (*buff != '1' && *buff != '2') || buff[1] != ' ')\n      return( 0);    /* not a .TLE */\n   while( --count)\n      {\n      if( *buff < ' ' || *buff > 'z')\n         return( 0);             /* wups!  those shouldn't happen in .TLEs */\n      if( *buff > '0' && *buff <= '9')\n         rval += *buff - '0';\n      else if( *buff == '-')\n         rval++;\n      buff++;\n      }\n   if( *buff != 10 && buff[1] != 10 && buff[2] != 10)\n      return( 0);                              /* _still_ not a .TLE */\n   *buff++ = (char)( '0' + (rval % 10));\n   *buff++ = 13;\n   *buff++ = 10;\n   *buff++ = '\\0';\n   return( 1);\n}\n\nstatic double zero_to_two_pi( double angle_in_radians)\n{\n   angle_in_radians = fmod( angle_in_radians, PI + PI);\n   if( angle_in_radians < 0.)\n      angle_in_radians += PI + PI;\n   return( angle_in_radians);\n}\n\n/* See comments for get_high_value() in 'get_el.cpp'.  Essentially,  we are\n   writing out a state vector in a convoluted base-36 form.  */\n\nstatic void set_high_value( char *obuff, const double value)\n{\n   int64_t oval = (int64_t)fabs( value);\n   int i;\n\n   *obuff++ = (value >= 0. ? '+' : '-');\n   for( i = 7; i >= 0; i--, oval /= (int64_t)36)\n      {\n      obuff[i] = (char)( oval % (int64_t)36);\n      if( obuff[i] < 10)\n         obuff[i] += '0';\n      else\n         obuff[i] += 'A' - 10;\n      }\n   obuff[8] = ' ';\n}\n\n\n/* The second derivative of the mean motion,  'xnddo6',  and the 'bstar'\ndrag term,  are stored in a simplified scientific notation.  To save\nvaluable bytes,  the decimal point and 'E' are assumed.     */\n\nstatic void put_sci( char *obuff, double ival)\n{\n   if( !ival)\n      memcpy( obuff, \" 00000-0\", 7);\n   else\n      {\n      int oval, exponent = 0;\n\n      if( ival > 0.)\n         *obuff++ = ' ';\n      else\n         {\n         *obuff++ = '-';\n         ival = -ival;\n         }\n      while( 1)\n         {\n         if( ival > 1.)    /* avoid integer overflow */\n            oval = 100000;\n         else\n            oval = (int)( ival * 100000. + .5);\n         if( oval > 99999)\n            {\n            ival /= 10;\n            exponent++;\n            }\n         else if( oval < 10000)\n            {\n            ival *= 10;\n            exponent--;\n            }\n         else\n            break;\n         }\n      snprintf( obuff, 7, \"%5d\", oval);\n      assert( 5 == strlen( obuff));\n      if( exponent > 0)\n         {\n         obuff[5] = '+';\n         obuff[6] = (char)( '0' + exponent);\n         }\n      else\n         {\n         obuff[5] = '-';\n         obuff[6] = (char)( '0' - exponent);\n         }\n      }\n}\n\n/* See comments for get_norad_number( ) in get_el.cpp.  This\nperforms the reverse function of setting the five bytes corresponding\nto a NORAD number,  using the 'standard' Alpha-5 method for numbers\n0 to 339000 and the nonstandard Super-5 method beyond that. */\n\nstatic char int_to_base64( const int digit)\n{\n   int rval;\n\n   assert( digit >= 0 && digit < 64);\n   if( digit < 0 || digit >= 64)\n      rval = ' ';\n   else if( digit < 10)\n      rval = '0' + digit;\n   else if( digit < 36)\n      rval = 'A' + digit - 10;\n   else if( digit < 62)\n      rval = 'a' + digit - 36;\n   else\n      rval = (digit == 62 ? '+' : '-');\n   return( rval);\n}\n\nstatic void store_norad_number_in_alpha5( char *obuff, const int norad_number)\n{\n   const int N_TYPE_STANDARD = 340000;         /* five digits plus Alpha-5 */\n   const int N_TYPE_2 = 64 * 64 * 64 * 64 * 54;       /* xxxxL */\n/* const int N_TYPE_3 = 64 * 64 * 64 * 54 * 10;          xxxLd;  we don't actually use this */\n   const int one_billion = 1000000000;\n   int i, tval = norad_number;\n\n   assert( norad_number >= 0 && norad_number < one_billion);\n   if( norad_number < 0 || norad_number >= one_billion)\n      strcpy( obuff, \"     \");      /* outside representable range */\n   else if( norad_number < N_TYPE_STANDARD)\n      {\n      for( i = 4; i > 0; i--, tval /= 10)\n         obuff[i] = '0' + (tval % 10);\n      *obuff = int_to_base64( tval);\n      if( *obuff >= 'I')\n         (*obuff)++;\n      if( *obuff >= 'O')\n         (*obuff)++;\n      }\n   else if( norad_number < N_TYPE_STANDARD + N_TYPE_2)\n      {\n      tval -= N_TYPE_STANDARD;\n      obuff[4] = int_to_base64( tval % 54 + 10);\n      tval /= 54;\n      for( i = 3; i >= 0; i--, tval >>= 6)\n         obuff[i] = int_to_base64( tval & 0x3f);\n      }\n   else\n      {\n      tval -= N_TYPE_STANDARD + N_TYPE_2;\n      obuff[4] = int_to_base64( tval % 10);\n      obuff[3] = int_to_base64( (tval / 10) % 54 + 10);\n      tval /= 540;\n      for( i = 2; i >= 0; i--, tval >>= 6)\n         obuff[i] = int_to_base64( tval & 0x3f);\n      }\n   obuff[5] = '\\0';\n}\n\n/* SpaceTrack TLEs have,  on the second line,  leading zeroes in front of the\ninclination,  ascending node,  argument of perigee,  and mean motion.  Which\nis why I've used this format string :\n\n   snprintf( line2 + 8, 57, \"%08.4f %08.4f %07ld %08.4f %08.4f %011.8f\", ...)\n\n   'classfd.tle' and some other sources don't use leading zeroes.  For them,\none should use the following format string for those four quantities :\n\n   snprintf( line2 + 8, 57, \"%8.4f %8.4f %07ld %8.4f %8.4f %11.8f\", ...)  */\n\nvoid DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle)\n{\n   long year = (long)( tle->epoch - J1900) / 365 + 1;\n   double day_of_year;\n   char *line2, norad_num_text[6];\n\n   do\n      {\n      double start_of_year;\n\n      year--;\n      start_of_year = J1900 + (double)year * 365. + (double)((year - 1) / 4);\n      day_of_year = tle->epoch - start_of_year;\n      }\n      while( day_of_year < 1.);\n   if( year < 0 || year > 200)      /* bogus input */\n      {\n      year = 56;\n      day_of_year = 0.;\n      }\n   store_norad_number_in_alpha5( norad_num_text, tle->norad_number);\n   snprintf( buff, 72,\n/*                                     xndt2o    xndd6o   bstar  eph bull */\n           \"1 %5s%c %-8s %02ld%12.8f -.000hit00 +00000-0 +00000-0 %c %4dZ\\n\",\n           norad_num_text, tle->classification, tle->intl_desig,\n           year % 100L, day_of_year,\n           tle->ephemeris_type, tle->bulletin_number);\n   if( buff[20] == ' ')       /* fill in leading zeroes for day of year */\n      buff[20] = '0';\n   if( buff[21] == ' ')\n      buff[21] = '0';\n   if( tle->ephemeris_type != 'H')     /* \"normal\",  standard TLEs */\n      {\n      double deriv_mean_motion = tle->xndt2o * MINUTES_PER_DAY_SQUARED / (2. * PI);\n      unsigned long lderiv;\n\n      if( deriv_mean_motion >= 0)\n         buff[33] = ' ';\n      lderiv = (unsigned long)( fabs( deriv_mean_motion * 100000000.) + .5);\n      assert( lderiv < 100000000);\n      snprintf( buff + 35, 10, \"%08lu\", lderiv);\n      assert( 8 == strlen( buff + 35));\n      buff[43] = ' ';\n      put_sci( buff + 44, tle->xndd6o * MINUTES_PER_DAY_CUBED / (2. * PI));\n      put_sci( buff + 53, tle->bstar / AE);\n      }\n   else\n      {\n      size_t i;\n      const double *posn = &tle->xincl;\n\n      for( i = 0; i < 3; i++)\n         set_high_value( buff + 33 + i * 10, posn[i]);\n      buff[62] = 'H';\n      }\n   add_tle_checksum_data( buff);\n   assert( 71 == strlen( buff));\n   line2 = buff + 71;\n   snprintf( line2, 10, \"2 %5s \", norad_num_text);\n   assert( 8 == strlen( line2));\n   if( tle->ephemeris_type != 'H')     /* \"normal\",  standard TLEs */\n      {\n      const double revs_per_day = tle->xno * MINUTES_PER_DAY / (2. * PI);\n\n      assert( revs_per_day > 0. && revs_per_day < 99.);\n      snprintf( line2 + 8, 57, \"%08.4f %08.4f %07ld %08.4f %08.4f %011.8f\",\n           zero_to_two_pi( tle->xincl) * 180. / PI,\n           zero_to_two_pi( tle->xnodeo) * 180. / PI,\n           (long)( tle->eo * 10000000. + .5),\n           zero_to_two_pi( tle->omegao) * 180. / PI,\n           zero_to_two_pi( tle->xmo) * 180. / PI,\n           revs_per_day);\n      assert( 55 == strlen( line2 + 8));\n      }\n   else\n      {\n      size_t i;\n      const double *vel = &tle->xincl + 3;\n\n      memset( line2 + 8, ' ', 25);     /* reserved for future use */\n      for( i = 0; i < 3; i++)\n         set_high_value( line2 + 33 + i * 10, vel[i] * 1e+4);\n      }\n   assert( tle->revolution_number >= 0 && tle->revolution_number < 100000);\n   snprintf( line2 + 63, 8, \"%5dZ\\n\", tle->revolution_number);\n   add_tle_checksum_data( line2);\n}\n"
  },
  {
    "path": "watcom.mak",
    "content": "# Makefile for OpenWATCOM\n\nall: test2.exe test_sat.exe obs_test.exe obs_tes2.exe sat_id.exe test_out.exe out_comp.exe\n\nout_comp.exe: out_comp.cpp\n   wcl386 -zq -W4 -Ox out_comp.cpp\n\ntest2.exe: test2.obj wsatlib.lib\n   wcl386 -zq -k10000 test2.obj wsatlib.lib\n\ntest_sat.exe: test_sat.obj wsatlib.lib\n   wcl386 -zq -k10000 test_sat.obj wsatlib.lib\n\nobs_test.exe: obs_test.obj wsatlib.lib\n   wcl386 -zq -k10000 obs_test.obj wsatlib.lib\n\nobs_tes2.exe: obs_tes2.obj wsatlib.lib\n   wcl386 -zq -k10000 obs_tes2.obj wsatlib.lib\n\nWAT_LIB=../watlib\n\nsat_id.exe: sat_id.obj sat_util.obj wsatlib.lib $(WAT_LIB)/wafuncs.lib\n   wcl386 -zq -k10000 sat_id.obj sat_util.obj wsatlib.lib $(WAT_LIB)/wafuncs.lib\n\ntest_out.exe: test_out.obj wsatlib.lib\n   wcl386 -zq -k10000 test_out.obj wsatlib.lib\n\n#CFLAGS=-W4 -Ox -j -zq -DRETAIN_PERTURBATION_VALUES_AT_EPOCH\nCFLAGS=-W4 -Ox -j -zq -i=..\\include\n\nwsatlib.lib: sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj &\n     basics.obj get_el.obj observe.obj common.obj tle_out.obj\n   wlib -q wsatlib.lib  +sgp.obj +sgp4.obj +sgp8.obj +sdp4.obj +sdp8.obj\n   wlib -q wsatlib.lib  +deep.obj +basics.obj +get_el.obj +observe.obj\n   wlib -q wsatlib.lib  +common.obj +tle_out.obj\n\n.cpp.obj:\n   wcc386 $(CFLAGS) $<\n\n.c.obj:\n   wcc386 $(CFLAGS) $<\n\nbasics.obj:\n\ndeep.obj:\n\nget_el.obj:\n\nobserve.obj:\n\ncommon.obj:\n\nobs_test.obj:\n\nobs_tes2.obj:\n\nsat_id.obj:\n\nsat_util.obj:\n\ntle_out.obj:\n\ntest_sat.obj:\n\ntest2.obj:\n\nsgp.obj:\n\nsgp4.obj:\n\nsgp8.obj:\n\nsdp4.obj:\n\nsdp8.obj:\n\ntest_out.obj:\n\nclean:\n   -del *.obj\n   -del *.exe\n   -del *.o\n   -del *.map\n   -del *.lib\n   -del *.exp\n   -del *.dll\n   -del *.a\n"
  },
  {
    "path": "zlibstub.h",
    "content": "#define gzFile FILE *\n#define gzopen fopen\n#define gzgets( ifile, buff, buffsize)    fgets( buff, buffsize, ifile)\n#define gzclose fclose\n"
  }
]