Full Code of Bill-Gray/sat_code for AI

master ff7b98957dfa cached
58 files
448.3 KB
149.9k tokens
167 symbols
1 requests
Download .txt
Showing preview only (467K chars total). Download the full file or copy to clipboard to get everything.
Repository: Bill-Gray/sat_code
Branch: master
Commit: ff7b98957dfa
Files: 58
Total size: 448.3 KB

Directory structure:
gitextract_zy817hdm/

├── .github/
│   └── workflows/
│       └── github_actions_build.yml
├── .gitignore
├── LICENSE
├── README.md
├── basics.cpp
├── common.cpp
├── deep.cpp
├── dropouts.c
├── dynamic.cpp
├── elem2tle.cpp
├── fake_ast.cpp
├── fix_tles.cpp
├── get_el.cpp
├── get_high.cpp
├── get_vect.cpp
├── line2.cpp
├── makefile
├── mergetle.cpp
├── msvc.mak
├── msvc_dll.mak
├── norad.h
├── norad_in.h
├── nu2vect.c
├── nu_readme.txt
├── obs_tes2.cpp
├── obs_test.cpp
├── obs_test.txt
├── observe.cpp
├── observe.h
├── out_comp.cpp
├── sat_code.def
├── sat_eph.c
├── sat_id.cpp
├── sat_id.txt
├── sat_id2.cpp
├── sat_id3.cpp
├── sat_util.c
├── sat_util.h
├── sdp4.cpp
├── sdp8.cpp
├── sgp.cpp
├── sgp4.cpp
├── sgp8.cpp
├── sm_sat.def
├── ssc_eph.c
├── summarize.c
├── test.tle
├── test2.cpp
├── test2.txt
├── test3.cpp
├── test_des.cpp
├── test_out.cpp
├── test_sat.cpp
├── tle2mpc.cpp
├── tle_date.c
├── tle_out.cpp
├── watcom.mak
└── zlibstub.h

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/github_actions_build.yml
================================================
# Shameless copy of @AlastairUK's fine work for the jpl_eph repo.
# Aside from the comment,  this is almost a byte-for-byte copy.
name: Build

on: [push, pull_request]

jobs:
  buildUbuntu:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: make
      run: |
        git clone https://github.com/Bill-Gray/lunar
        cd lunar
        make ERRORS=Y liblunar.a
        make install
        cd ..
        make ERRORS=Y

  buildOSX:
    runs-on: macOS-latest
    steps:
    - uses: actions/checkout@master
    - name: make
      run: |
        git clone https://github.com/Bill-Gray/lunar
        cd lunar
        make CC=clang ERRORS=Y liblunar.a
        make install
        cd ..
        make CC=clang ERRORS=Y

  buildWindows:
    runs-on: windows-latest
    steps:
    - uses: actions/checkout@master
    - name: make
      run: |
        call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64
        git clone https://github.com/Bill-Gray/lunar
        mkdir myincl
        cd lunar
        nmake -f lunar.mak lunar64.lib
        nmake -f lunar.mak install
        cd ..
        set CL=/I"./myincl"
        copy lunar\*.lib .
        nmake -f msvc.mak
      shell: cmd


================================================
FILE: .gitignore
================================================
# Compiled Object files
*.slo
*.lo
*.o
*.obj

# Precompiled Headers
*.gch
*.pch

# Compiled Dynamic libraries
*.so
*.dylib
*.dll

# Fortran module files
*.mod

# Compiled Static libraries
*.lai
*.la
*.a
*.lib

# Executables
*.exe
*.out
*.app
*.cgi
get_high
mergetle
obs_test
obs_tes2
out_comp
sat_id
sat_id2
test2
test_out
test_sat
tle_date


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020,  Project Pluto

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# sat_code

C/C++ code for the SGP4/SDP4 satellite motion model,  and for manipulating TLEs
(Two-Line Elements). Full details at
[http://www.projectpluto.com/sat_code.htm](http://www.projectpluto.com/sat_code.htm).

The only dependency is on the [`lunar`](https://github.com/Bill-Gray/lunar) (basic
astronomical ephemeris/time functions) library.  Make and `make install` that
library before attempting to build this code.

On Linux,  run `make` to build the library and various test executables.
(You can also do this with MinGW under Windows.)  In Linux,  you
can then run `make install` to put libraries in `/usr/local/lib` and some
include files in `/usr/local/include`.  (You will probably have to make that
`sudo make install`.)  For BSD,  and probably OS/X,  run `gmake CLANG=Y`
(GNU make,  with the clang compiler),  then `sudo gmake install`.

On Windows,  run `nmake -f msvc.mak` with MSVC++.  Optionally,  add
`-BITS_32=Y` for 32-bit code.


================================================
FILE: basics.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

#include <math.h>
#include "norad.h"
#include "norad_in.h"

/*------------------------------------------------------------------*/

/* FMOD2P */
double FMod2p( const double x)
{
   double rval = fmod( x, twopi);

   if( rval < 0.)
      rval += twopi;
   return( rval);
} /* fmod2p */

#define EPHEM_TYPE_DEFAULT    '0'
#define EPHEM_TYPE_SGP        '1'
#define EPHEM_TYPE_SGP4       '2'
#define EPHEM_TYPE_SDP4       '3'
#define EPHEM_TYPE_SGP8       '4'
#define EPHEM_TYPE_SDP8       '5'
#define EPHEM_TYPE_HIGH       'H'

/*------------------------------------------------------------------*/

void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg);
                                                            /* common.c */

/* Selects the type of ephemeris to be used (SGP*-SDP*) */
int DLL_FUNC select_ephemeris( const tle_t *tle)
{
   int rval;

   if( tle->ephemeris_type == EPHEM_TYPE_HIGH)
      rval = 1;         /* force high-orbit state vector model */
   else if( tle->xno <= 0. || tle->eo > 1. || tle->eo < 0.)
      rval = -1;                 /* error in input data */
   else if( tle->ephemeris_type == EPHEM_TYPE_SGP4
         || tle->ephemeris_type == EPHEM_TYPE_SGP8)
      rval = 0;         /* specifically marked non-deep */
   else if( tle->ephemeris_type == EPHEM_TYPE_SDP4
         || tle->ephemeris_type == EPHEM_TYPE_SDP8)
      rval = 1;         /* specifically marked deep */
   else
      {
      deep_arg_t deep_arg;

      sxpall_common_init( tle, &deep_arg);
      /* Select a deep-space/near-earth ephemeris */
      /* If the orbital period is greater than 225 minutes... */
      if (twopi / deep_arg.xnodp >= 225.)
         rval = 1;      /* yes,  it should be a deep-space (SDPx) ephemeris */
      else
         rval = 0;      /* no,  you can go with an SGPx ephemeris */
      }
   return( rval);
} /* End of select_ephemeris() */

/*------------------------------------------------------------------*/

long DLL_FUNC sxpx_library_version( void)
{
   return( 0x100);
}


================================================
FILE: common.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE. */

#include <math.h>
#include <assert.h>
#include "norad.h"
#include "norad_in.h"

/* params[1] and [6]-[9] were used in earlier implementations,  but are
   now unused */

#define c2           params[0]
#define c1           params[2]
#define c4           params[3]
#define xnodcf       params[4]
#define t2cof        params[5]

void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg)
{
   const double a1 = pow(xke / tle->xno, two_thirds);  /* in Earth radii */
   double del1, ao, delo, tval;

   /* Recover original mean motion (xnodp) and   */
   /* semimajor axis (aodp) from input elements. */
   deep_arg->cosio = cos( tle->xincl);
   deep_arg->cosio2 = deep_arg->cosio * deep_arg->cosio;
   deep_arg->eosq = tle->eo*tle->eo;
   deep_arg->betao2 = 1-deep_arg->eosq;
   deep_arg->betao = sqrt(deep_arg->betao2);
   tval = 1.5 * ck2 * (3. * deep_arg->cosio2 - 1.) / (deep_arg->betao * deep_arg->betao2);
   del1 = tval / (a1 * a1);
   ao = a1 * (1. - del1 * (1. / 3. + del1 * ( 1. + 134./81. * del1)));
   delo = tval / (ao * ao);
   deep_arg->xnodp = tle->xno / (1+delo);   /* in radians/minute */
   deep_arg->aodp = ao / (1-delo);
}

void sxpx_common_init( double *params, const tle_t *tle,
                                  init_t *init, deep_arg_t *deep_arg)
{
   double
         eeta, etasq, perige, pinv, pinvsq,
         psisq, qoms24, temp1, temp2, temp3,
         cosio4, tsi_squared, x3thm1, xhdot1;

   sxpall_common_init( tle, deep_arg);
   x3thm1 = 3. * deep_arg->cosio2 - 1.;
   /* For perigee below 156 km, the values */
   /* of s and qoms2t are altered.         */
   init->s4 = s_const;
   qoms24 = qoms2t;
   perige = (deep_arg->aodp * (1-tle->eo) - ae) * earth_radius_in_km;
   if( perige < 156.)
      {
      double temp_val, temp_val_squared;

      if(perige <= 98.)
         init->s4 = 20;
      else
         init->s4 = perige-78.;
      temp_val = (120. - init->s4) * ae / earth_radius_in_km;
      temp_val_squared = temp_val * temp_val;
      qoms24 = temp_val_squared * temp_val_squared;
      init->s4 = init->s4 / earth_radius_in_km + ae;
      }  /* End of if(perige <= 156) */

   pinv = 1. / (deep_arg->aodp * deep_arg->betao2);
   pinvsq = pinv * pinv;
   init->tsi = 1. / (deep_arg->aodp - init->s4);
   init->eta = deep_arg->aodp*tle->eo*init->tsi;
   etasq = init->eta*init->eta;
   eeta = tle->eo*init->eta;
   psisq = fabs(1-etasq);
   tsi_squared = init->tsi * init->tsi;
   init->coef = qoms24 * tsi_squared * tsi_squared;
   init->coef1 = init->coef / pow(psisq,3.5);
   c2 = init->coef1 * deep_arg->xnodp * (deep_arg->aodp*(1+1.5*etasq+eeta*
   (4+etasq))+0.75*ck2*init->tsi/psisq*x3thm1*(8+3*etasq*(8+etasq)));
   c1 = tle->bstar*c2;
   deep_arg->sinio = sin(tle->xincl);
   c4 = 2*deep_arg->xnodp*init->coef1*deep_arg->aodp*deep_arg->betao2*
        (init->eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*init->tsi/
        (deep_arg->aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq*
        (1.5-0.5*eeta))+0.75*(1. - deep_arg->cosio2) *(2*etasq-eeta*(1+etasq))*
        cos(2*tle->omegao)));
   cosio4 = deep_arg->cosio2 * deep_arg->cosio2;
   temp1 = 3*ck2*pinvsq*deep_arg->xnodp;
   temp2 = temp1 * ck2 * pinvsq;
   temp3 = 1.25 * ck4 * pinvsq * pinvsq * deep_arg->xnodp;
   deep_arg->xmdot = deep_arg->xnodp
            + temp1 * deep_arg->betao* x3thm1 / 2.
            + temp2 * deep_arg->betao*
                    (13-78*deep_arg->cosio2+137*cosio4) / 16.;
   deep_arg->omgdot = -temp1 * (1. - 5 * deep_arg->cosio2) / 2.
              + temp2 * (7-114*deep_arg->cosio2+395*cosio4) / 16.
              + temp3 * (3-36*deep_arg->cosio2+49*cosio4);
   xhdot1 = -temp1*deep_arg->cosio;
   deep_arg->xnodot = xhdot1+(temp2*(4-19*deep_arg->cosio2) / 2.
           + 2*temp3*(3-7*deep_arg->cosio2))*deep_arg->cosio;
   xnodcf = 3.5*deep_arg->betao2*xhdot1*c1;
   t2cof = 1.5*c1;
}

inline double centralize_angle( const double ival)
{
   double rval = fmod( ival, twopi);

   if( rval > pi)
      rval -= twopi;
   else if( rval < - pi)
      rval += twopi;
   return( rval);
}

#define MAX_KEPLER_ITER 10

int sxpx_posn_vel( const double xnode, const double a, const double ecc,
      const double cosio, const double sinio,
      const double xincl, const double omega,
      const double xl, double *pos, double *vel)
{
  /* Long period periodics */
   const double axn = ecc*cos(omega);
   double temp = 1/(a*(1.-ecc*ecc));
   const double xlcof = .125 * a3ovk2 * sinio * (3+5*cosio)/ (1. + cosio);
   const double aycof = 0.25 * a3ovk2 * sinio;
   const double xll = temp*xlcof*axn;
   const double aynl = temp*aycof;
   const double xlt = xl+xll;
   const double ayn = ecc*sin(omega)+aynl;
   const double elsq = axn*axn+ayn*ayn;
   const double capu = centralize_angle( xlt - xnode);
   const double chicken_factor_on_eccentricity = 1.e-6;
   double epw = capu;
   double temp1, temp2;
   double ecosE, esinE, pl, r;
   double betal;
   double u, sinu, cosu, sin2u, cos2u;
   double rk, uk, xnodek, xinck;
   double sinuk, cosuk, sinik, cosik, sinnok, cosnok, xmx, xmy;
   double sinEPW, cosEPW;
   double ux, uy, uz;
   int i, rval = 0;

/* Dundee changes:  items dependent on cosio get recomputed: */
   const double cosio_squared = cosio * cosio;
   const double x3thm1 = 3.0 * cosio_squared - 1.0;
   const double sinio2 = 1.0 - cosio_squared;
   const double x7thm1 = 7.0 * cosio_squared - 1.0;

                /* Added 29 Mar 2003,  modified 26 Sep 2006:  extremely    */
                /* decayed satellites can end up "orbiting" within the     */
                /* earth.  Eventually,  the semimajor axis becomes zero,   */
                /* then negative.  In that case,  or if the orbit is near  */
                /* to parabolic,  we zero the posn/vel and quit.  If the   */
                /* object has a perigee or apogee indicating a crash,  we  */
                /* just flag it.  Revised 28 Oct 2006.                     */

   if( a < 0.)
      rval = SXPX_ERR_NEGATIVE_MAJOR_AXIS;
   if( elsq > 1. - chicken_factor_on_eccentricity)
      rval = SXPX_ERR_NEARLY_PARABOLIC;
   for( i = 0; i < 3; i++)
      {
      pos[i] = 0.;
      if( vel)
         vel[i] = 0.;
      }
   if( rval)
      return( rval);
   if( a * (1. - ecc) < 1. && a * (1. + ecc) < 1.)   /* entirely within earth */
      rval = SXPX_WARN_ORBIT_WITHIN_EARTH;     /* remember, e can be negative */
   if( a * (1. - ecc) < 1. || a * (1. + ecc) < 1.)   /* perigee within earth */
      rval = SXPX_WARN_PERIGEE_WITHIN_EARTH;
  /* Solve Kepler's' Equation */
   for( i = 0; i < MAX_KEPLER_ITER; i++)
      {
      const double newton_raphson_epsilon = 1e-12;
      double f, fdot, delta_epw;
      int do_second_order_newton_raphson = 1;

      sinEPW = sin( epw);
      cosEPW = cos( epw);
      ecosE = axn * cosEPW + ayn * sinEPW;
      esinE = axn * sinEPW - ayn * cosEPW;
      f = capu - epw + esinE;
      if (fabs(f) < newton_raphson_epsilon) break;
      fdot = 1. - ecosE;
      delta_epw = f / fdot;
      if( !i)
         {
         const double max_newton_raphson = 1.25 * fabs( ecc);

         do_second_order_newton_raphson = 0;
         if( delta_epw > max_newton_raphson)
            delta_epw = max_newton_raphson;
         else if( delta_epw < -max_newton_raphson)
            delta_epw = -max_newton_raphson;
         else
            do_second_order_newton_raphson = 1;
         }
      if( do_second_order_newton_raphson)
         delta_epw = f / (fdot + 0.5*esinE*delta_epw);
                             /* f/(fdot - 0.5*fdotdot * f / fdot) */
      epw += delta_epw;
      }

   if( i == MAX_KEPLER_ITER)
      return( SXPX_ERR_CONVERGENCE_FAIL);

  /* Short period preliminary quantities */
   temp = 1-elsq;
   pl = a*temp;
   r = a*(1-ecosE);
   temp2 = a / r;
   betal = sqrt(temp);
   temp = esinE/(1+betal);
   cosu = temp2 * (cosEPW - axn + ayn * temp);
   sinu = temp2 * (sinEPW - ayn - axn * temp);
   u = atan2( sinu, cosu);
   sin2u = 2*sinu*cosu;
   cos2u = 2*cosu*cosu-1;
   temp1 = ck2 / pl;
   temp2 = temp1 / pl;

  /* Update for short periodics */
   rk = r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*sinio2*cos2u;
   uk = u-0.25*temp2*x7thm1*sin2u;
   xnodek = xnode+1.5*temp2*cosio*sin2u;
   xinck = xincl+1.5*temp2*cosio*sinio*cos2u;

  /* Orientation vectors */
   sinuk = sin(uk);
   cosuk = cos(uk);
   sinik = sin(xinck);
   cosik = cos(xinck);
   sinnok = sin(xnodek);
   cosnok = cos(xnodek);
   xmx = -sinnok*cosik;
   xmy = cosnok*cosik;
   ux = xmx*sinuk+cosnok*cosuk;
   uy = xmy*sinuk+sinnok*cosuk;
   uz = sinik*sinuk;

  /* Position and velocity */
   pos[0] = rk * ux * earth_radius_in_km;
   pos[1] = rk * uy * earth_radius_in_km;
   pos[2] = rk * uz * earth_radius_in_km;
   if( vel)
      {
      const double rdot = xke * sqrt(a) * esinE / r;
      const double rfdot = xke * sqrt(pl) / r;
      const double xn = xke / (a * sqrt(a));
      const double rdotk = rdot - xn * temp1 * sinio2 * sin2u;
      const double rfdotk = rfdot + xn * temp1 * (sinio2 * cos2u + 1.5 * x3thm1);
      const double vx = xmx * cosuk - cosnok * sinuk;
      const double vy = xmy * cosuk - sinnok * sinuk;
      const double vz = sinik*cosuk;

      vel[0] = (rdotk * ux + rfdotk * vx) * earth_radius_in_km;
      vel[1] = (rdotk * uy + rfdotk * vy) * earth_radius_in_km;
      vel[2] = (rdotk * uz + rfdotk * vz) * earth_radius_in_km;
      }
   return( rval);
} /*SGP4*/


================================================
FILE: deep.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE. */

#include <math.h>
#include "norad.h"
#include "norad_in.h"

      /* omega_E = number of (sidereal) rotations of the earth per UT day: */
const double omega_E = 1.00273790934;
#ifdef USE_ACCURATE_ANOMALISTICS
         /* The anomalistic month is the mean time it takes the moon to go
            from perigee to perigee.  The anomalistic year is the mean time
            it takes the earth to go from perihelion to perihelion.
            The following lines compute the "correct" mean motions of
            the earth and sun: zns_per_day is the rate of change of
            the earth's mean anomaly,  in radians per day,  and the 'znl'
            quantities give similar rates for the moon.
               Problem is,  the original SxPx sources give values that are
            close to,  but not exactly equal to,  these values.  The
            "new" values are probably improvements from further observations,
            but if you actually used them,  you'd break compatibility with
            older implementations,  and wouldn't match up with the way
            NORAD and others actually compute TLEs.  So the following few
            lines should be regarded as explanatory;  we're stuck with using
            the older,  less accurate SxPx values.  */
const double days_per_anomalistic_month =  27.554551;
const double days_per_anomalistic_year = 365.259635864;
const double zns_per_day = twopi / days_per_anomalistic_year;
const double zns_per_min = zns_per_day / minutes_per_day;
const double znl_per_day = twopi / days_per_anomalistic_month;
const double znl_per_min = znl_per_day / minutes_per_day;
         /* thdt = angular velocity of the earth,  in radians/minute. */
         /* Again,  we have to use a less accurate value from the original */
         /* SxPx,  to replicate everybody else's results.                  */
const double thdt = twopi * omega_E / minutes_per_day;
#else
const double zns_per_min = 1.19459E-5;
const double zns_per_day = 0.017201977;
const double znl_per_day = 0.228027132;
const double znl_per_min = 1.5835218E-4;
const double thdt =   4.37526908801129966e-3;
#endif
         /* zes = mean eccentricity of earth's orbit */
         /* zel = mean eccentricity of the moon's orbit */
#define zes      0.01675
#define zel      0.05490

/* thetag: computes Greenwich sidereal time,  as an angle in radians
from 0 to 2*pi,  for a given UT0 JD. */

static inline double ThetaG( const double jd)
{
                 /* Reference:  The 1992 Astronomical Almanac, page B6. */
                 /* Earth rotations per sidereal day (non-constant) */
  const double UT = fmod( jd + .5, 1.);
  const double seconds_per_day = 86400.;
  const double jd_2000 = 2451545.0;   /* 1.5 Jan 2000 = JD 2451545. */
  double t_cen, GMST, rval;

  t_cen = (jd - UT - jd_2000) / 36525.;
  GMST = 24110.54841 + t_cen * (8640184.812866 + t_cen *
                           (0.093104 - t_cen * 6.2E-6));
  GMST = fmod( GMST / seconds_per_day + omega_E * UT, 1.);
  if( GMST < 0.)
     GMST += 1.;
  rval = twopi * GMST;

  return( rval);
} /*Function thetag*/

      /* Previously,  the integration step was given as two variables:      */
      /* 'stepp' (positive step = +720) and 'stepn' (negative step = -720). */
      /* Exactly why this should be made a variable,  much less _different_ */
      /* variables for positive and negative,  is entirely unclear...       */
      /* (8 Apr 2003) INTEGRATION_STEP is now a maximum integration step.   */
      /* The code in 'dpsec' splits the integration range into equally-sized */
      /* pieces of 720 minutes (half a day) or smaller.                      */
      /* (25 Aug 2006) INTEGRATION_STEP is now the variable                  */
      /* 'dpsec_integration_step' so I can experiment with different         */
      /* integration techniques & evaluate their errors.                     */

static double dpsec_integration_step = 720.;
static int dpsec_integration_order = 2;
static int is_dundee_compliant = 0;

void DLL_FUNC sxpx_set_implementation_param( const int param_index,
                                              const int new_param)
{
   switch( param_index)
      {
      case SXPX_DPSEC_INTEGRATION_ORDER:
         dpsec_integration_order = new_param;
         break;
      case SXPX_DUNDEE_COMPLIANCE:
         is_dundee_compliant = new_param;
         break;
      }
}

void DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size)
{
   dpsec_integration_step = new_step_size;
}

static inline double eval_cubic_poly( const double x, const double constant,
               const double linear, const double quadratic_term,
               const double cubic_term)
{
   return( constant + x * (linear + x * (quadratic_term + x * cubic_term)));
}

/* DEEP */
void Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg)
{
   const double sinq = sin(tle->xnodeo);
   const double cosq = cos(tle->xnodeo);
   const double aqnv = 1/deep_arg->aodp;
   const double c1ss   =  2.9864797E-6;
           /* 1900 Jan 0.5 = JD 2415020. */
   const double days_since_1900 = tle->epoch - 2415020.;
           /* zcosi, zsini start as cos & sin of obliquity of earth's  */
           /* orbit = 23.444100 degrees... matches obliquity in 1963; */
           /* probably just a slightly inaccurate value: */
   const double zcosi0 = 0.91744867;
   const double zsini0 = 0.39785416;
   double zcosi = zcosi0;
   double zsini = zsini0;
           /* zcosg, zsing start as cos & sin of -78.779197 degrees */
   double zsing = -0.98088458;
   double zcosg =  0.1945905;
   double bfact, cc = c1ss, se;
   double ze = zes, zn = zns_per_min;
   double sgh, sh, si;
   double zsinh = sinq, zcosh = cosq;
   double zcosil, zsinil, zcoshl, zsinhl;
   double zcosgl, zsingl;
   double sl;
   int iteration;

   deep_arg->thgr = ThetaG( tle->epoch);
   deep_arg->xnq = deep_arg->xnodp;
   deep_arg->omegaq = tle->omegao;

/* if( days_since_1900 != deep_arg->preep)   */
      {
      const double lunar_asc_node = 4.5236020 - 9.2422029E-4 * days_since_1900;
      const double sin_asc_node = sin(lunar_asc_node);
      const double cos_asc_node = cos(lunar_asc_node);
      const double c_minus_gam = znl_per_day * days_since_1900 - 1.1151842;
            /* gam = longitude of perigee for the moon,  in radians: */
      const double gam = 5.8351514 + 0.0019443680 * days_since_1900;
      double zx, zy;

      deep_arg->preep = days_since_1900;
      zcosil = 0.91375164 - 0.03568096 * cos_asc_node;
      zsinil = sqrt(1. - zcosil * zcosil);
      zsinhl = 0.089683511 * sin_asc_node / zsinil;
      zcoshl = sqrt(1. - zsinhl*zsinhl);
      deep_arg->zmol = FMod2p( c_minus_gam);
      zx = zsini0 * sin_asc_node / zsinil;
      zy = zcoshl * cos_asc_node + zcosi0 * zsinhl * sin_asc_node;
      zx = atan2( zx, zy) + gam - lunar_asc_node;
      zcosgl = cos( zx);
      zsingl = sin( zx);
      deep_arg->zmos = FMod2p( 6.2565837
                     + zns_per_day * days_since_1900);
      } /* End if( days_since_1900 != deep_arg->preep) */

   /* Do solar terms */
   deep_arg->savtsn = 1E20;

   /* There was previously some convoluted logic here,  but it boils    */
   /* down to this:  we compute the solar terms,  then the lunar terms. */
   /* On a second pass,  we recompute the solar terms,  taking advantage */
   /* of the improved data that resulted from computing lunar terms.     */
   for( iteration = 0; iteration < 2; iteration++)
      {
      const double c1l = 4.7968065E-7;
      const double a1 = zcosg * zcosh + zsing * zcosi * zsinh;
      const double a3 = -zsing * zcosh + zcosg * zcosi * zsinh;
      const double a7 = -zcosg * zsinh + zsing * zcosi * zcosh;
      const double a8 = zsing * zsini;
      const double a9 = zsing * zsinh + zcosg * zcosi * zcosh;
      const double a10 = zcosg * zsini;
      const double a2 = deep_arg->cosio * a7 + deep_arg->sinio * a8;
      const double a4 = deep_arg->cosio * a9 + deep_arg->sinio * a10;
      const double a5 = -deep_arg->sinio * a7 + deep_arg->cosio * a8;
      const double a6 = -deep_arg->sinio * a9 + deep_arg->cosio * a10;
      const double x1 = a1 * deep_arg->cosg + a2 * deep_arg->sing;
      const double x2 = a3 * deep_arg->cosg + a4 * deep_arg->sing;
      const double x3 = -a1 * deep_arg->sing + a2 * deep_arg->cosg;
      const double x4 = -a3 * deep_arg->sing + a4 * deep_arg->cosg;
      const double x5 = a5 * deep_arg->sing;
      const double x6 = a6 * deep_arg->sing;
      const double x7 = a5 * deep_arg->cosg;
      const double x8 = a6 * deep_arg->cosg;
      const double z31 = 12 * x1 * x1 - 3 * x3 * x3;
      const double z32 = 24 * x1 * x2 - 6 * x3 * x4;
      const double z33 = 12 * x2 * x2 - 3 * x4 * x4;
      const double z11 = -6 * a1 * a5 + deep_arg->eosq * (-24 * x1 * x7 - 6 * x3 * x5);
      const double z12 = -6 * (a1 * a6 + a3 * a5) +  deep_arg->eosq *
                (-24 * (x2 * x7 + x1 * x8) - 6 * (x3 * x6 + x4 * x5));
      const double z13 = -6 * a3 * a6 + deep_arg->eosq * (-24 * x2 * x8 - 6 * x4 * x6);
      const double z21 = 6 * a2 * a5 + deep_arg->eosq * (24 * x1 * x5 - 6 * x3 * x7);
      const double z22 = 6 * (a4 * a5 + a2 * a6) +  deep_arg->eosq *
                (24 * (x2 * x5 + x1 * x6) - 6 * (x4 * x7 + x3 * x8));
      const double z23 = 6 * a4 * a6 + deep_arg->eosq * (24 * x2 * x6 - 6 * x4 * x8);
      const double s3 = cc / deep_arg->xnq;
      const double s2 = -0.5 * s3 / deep_arg->betao;
      const double s4 = s3 * deep_arg->betao;
      const double s1 = -15 * tle->eo * s4;
      const double s5 = x1 * x3 + x2 * x4;
      const double s6 = x2 * x3 + x1 * x4;
      const double s7 = x2 * x4 - x1 * x3;
      double z1 = 3 * (a1 * a1 + a2 * a2) + z31 * deep_arg->eosq;
      double z2 = 6 * (a1 * a3 + a2 * a4) + z32 * deep_arg->eosq;
      double z3 = 3 * (a3 * a3 + a4 * a4) + z33 * deep_arg->eosq;

      z1 = z1 + z1 + deep_arg->betao2 * z31;
      z2 = z2 + z2 + deep_arg->betao2 * z32;
      z3 = z3 + z3 + deep_arg->betao2 * z33;
      se = s1*zn*s5;
      si = s2*zn*(z11+z13);
      sl = -zn*s3*(z1+z3-14-6*deep_arg->eosq);
      sgh = s4*zn*(z31+z33-6);
      if( tle->xincl < pi / 60.)      /* pi / 60 radians = 3 degrees */
         sh = 0;
      else
         sh = -zn*s2*(z21+z23);
      deep_arg->ee2 = 2*s1*s6;
      deep_arg->e3 = 2*s1*s7;
      deep_arg->xi2 = 2*s2*z12;
      deep_arg->xi3 = 2*s2*(z13-z11);
      deep_arg->xl2 = -2*s3*z2;
      deep_arg->xl3 = -2*s3*(z3-z1);
      deep_arg->xl4 = -2*s3*(-21-9*deep_arg->eosq)*ze;
      deep_arg->xgh2 = 2*s4*z32;
      deep_arg->xgh3 = 2*s4*(z33-z31);
      deep_arg->xgh4 = -18*s4*ze;
      deep_arg->xh2 = -2*s2*z22;
      deep_arg->xh3 = -2*s2*(z23-z21);

      if( !iteration)   /* we compute lunar terms only on the first pass: */
         {
         deep_arg->sse = se;
         deep_arg->ssi = si;
         deep_arg->ssl = sl;
         deep_arg->ssh = (deep_arg->sinio ? sh / deep_arg->sinio : 0.);
         deep_arg->ssg = sgh-deep_arg->cosio*deep_arg->ssh;
         deep_arg->se2 = deep_arg->ee2;
         deep_arg->si2 = deep_arg->xi2;
         deep_arg->sl2 = deep_arg->xl2;
         deep_arg->sgh2 = deep_arg->xgh2;
         deep_arg->sh2 = deep_arg->xh2;
         deep_arg->se3 = deep_arg->e3;
         deep_arg->si3 = deep_arg->xi3;
         deep_arg->sl3 = deep_arg->xl3;
         deep_arg->sgh3 = deep_arg->xgh3;
         deep_arg->sh3 = deep_arg->xh3;
         deep_arg->sl4 = deep_arg->xl4;
         deep_arg->sgh4 = deep_arg->xgh4;
         zcosg = zcosgl;
         zsing = zsingl;
         zcosi = zcosil;
         zsini = zsinil;
         zcosh = zcoshl * cosq + zsinhl * sinq;
         zsinh = sinq * zcoshl - cosq * zsinhl;
         zn = znl_per_min;
         cc = c1l;
         ze = zel;
         }
      }

   deep_arg->sse += se;
   deep_arg->ssi += si;
   deep_arg->ssl += sl;
   deep_arg->ssg += sgh;
   if( deep_arg->sinio)
      {
      deep_arg->ssg -= sh * deep_arg->cosio / deep_arg->sinio;
      deep_arg->ssh += sh / deep_arg->sinio;
      }

         /* "if mean motion is 1.893053 to 2.117652 revs/day, and ecc >= .5" */
   if( deep_arg->xnq >= 0.00826 && deep_arg->xnq <= 0.00924 && tle->eo >= .5)
      {           /* start of 12-hour orbit, e >.5 section */
                  /* 'root##' variables are somewhat inaccurate values for */
                  /* a few fully normalized sectorial/tesseral spherical   */
                  /* harmonics of the Earth's gravitational potential:     */
      const double root22 = 1.7891679E-6;
      const double root32 = 3.7393792E-7;
      const double root44 = 7.3636953E-9;
      const double root52 = 1.1428639E-7;
      const double root54 = 2.1765803E-9;
      const double g201 = -0.306 - (tle->eo - 0.64) * 0.440;
      const double sini2 = deep_arg->sinio*deep_arg->sinio;
      const double f220 = 0.75*(1+2*deep_arg->cosio+deep_arg->cosio2);
      const double f221 = 1.5 * sini2;
      const double f321 = 1.875 * deep_arg->sinio * (1 - 2 *\
               deep_arg->cosio - 3 * deep_arg->cosio2);
      const double f322 = -1.875*deep_arg->sinio*(1+2*
               deep_arg->cosio-3*deep_arg->cosio2);
      const double f441 = 35 * sini2 * f220;
      const double f442 = 39.3750 * sini2 * sini2;
      const double f522 = 9.84375*deep_arg->sinio*(sini2*(1-2*deep_arg->cosio-5*
                 deep_arg->cosio2)+0.33333333*(-2+4*deep_arg->cosio+
                 6*deep_arg->cosio2));
      const double f523 = deep_arg->sinio*(4.92187512*sini2*(-2-4*
                 deep_arg->cosio+10*deep_arg->cosio2)+6.56250012
                 *(1+2*deep_arg->cosio-3*deep_arg->cosio2));
      const double f542 = 29.53125*deep_arg->sinio*(2-8*
                 deep_arg->cosio+deep_arg->cosio2*
                 (-12+8*deep_arg->cosio+10*deep_arg->cosio2));
      const double f543 = 29.53125*deep_arg->sinio*(-2-8*deep_arg->cosio+
                 deep_arg->cosio2*(12+8*deep_arg->cosio-10*
                 deep_arg->cosio2));
      double g410, g422, g520, g521, g532, g533;
      double g211, g310, g322;
      double temp, temp1;

      deep_arg->resonance_flag = 1;       /* it _is_ resonant... */
      deep_arg->synchronous_flag = 0;     /* but it's not synchronous */
             /* Geopotential resonance initialization for 12 hour orbits: */
      if (tle->eo <= 0.65)
         {
         g211 = 3.616-13.247*tle->eo+16.290*deep_arg->eosq;
         g310 = eval_cubic_poly( tle->eo, -19.302, 117.390, -228.419, 156.591);
         g322 = eval_cubic_poly( tle->eo, -18.9068, 109.7927, -214.6334, 146.5816);
         g410 = eval_cubic_poly( tle->eo, -41.122, 242.694, -471.094, 313.953);
         g422 = eval_cubic_poly( tle->eo, -146.407, 841.880, -1629.014, 1083.435);
         g520 = eval_cubic_poly( tle->eo, -532.114, 3017.977, -5740.032, 3708.276);
                               /* NOTE: quadratic coeff was 5740 */
         }
      else
         {
         g211 = eval_cubic_poly( tle->eo, -72.099, 331.819, -508.738, 266.724);
         g310 = eval_cubic_poly( tle->eo, -346.844, 1582.851, -2415.925, 1246.113);
         g322 = eval_cubic_poly( tle->eo, -342.585, 1554.908, -2366.899, 1215.972);
         g410 = eval_cubic_poly( tle->eo, -1052.797, 4758.686, -7193.992, 3651.957);
         g422 = eval_cubic_poly( tle->eo, -3581.69, 16178.11, -24462.77, 12422.52);
         if (tle->eo <= 0.715)
            g520 = eval_cubic_poly( tle->eo, 1464.74, -4664.75, 3763.64, 0.);
         else
            g520 = eval_cubic_poly( tle->eo, -5149.66, 29936.92, -54087.36, 31324.56);
         } /* End if (tle->eo <= 0.65) */

      if (tle->eo < 0.7)
         {
         g533 = eval_cubic_poly( tle->eo, -919.2277, 4988.61, -9064.77, 5542.21);
         g521 = eval_cubic_poly( tle->eo, -822.71072, 4568.6173, -8491.4146, 5337.524);
         g532 = eval_cubic_poly( tle->eo, -853.666, 4690.25, -8624.77, 5341.4);
         }
      else
         {
         g533 = eval_cubic_poly( tle->eo, -37995.78, 161616.52, -229838.2, 109377.94);
         g521 = eval_cubic_poly( tle->eo, -51752.104, 218913.95, -309468.16, 146349.42);
         g532 = eval_cubic_poly( tle->eo, -40023.88, 170470.89, -242699.48, 115605.82);
         } /* End if (tle->eo <= 0.7) */

      temp1 = 3 * deep_arg->xnq * deep_arg->xnq * aqnv * aqnv;
      temp = temp1*root22;
      deep_arg->d2201 = temp * f220 * g201;
      deep_arg->d2211 = temp * f221 * g211;
      temp1 *= aqnv;
      temp = temp1*root32;
      deep_arg->d3210 = temp * f321 * g310;
      deep_arg->d3222 = temp * f322 * g322;
      temp1 *= aqnv;
      temp = 2*temp1*root44;
      deep_arg->d4410 = temp * f441 * g410;
      deep_arg->d4422 = temp * f442 * g422;
      temp1 *= aqnv;
      temp = temp1*root52;
      deep_arg->d5220 = temp * f522 * g520;
      deep_arg->d5232 = temp * f523 * g532;
      temp = 2*temp1*root54;
      deep_arg->d5421 = temp * f542 * g521;
      deep_arg->d5433 = temp * f543 * g533;
      deep_arg->xlamo = tle->xmo+tle->xnodeo+tle->xnodeo-deep_arg->thgr-deep_arg->thgr;
      bfact = deep_arg->xmdot + deep_arg->xnodot+
                   deep_arg->xnodot - thdt - thdt;
      bfact += deep_arg->ssl + deep_arg->ssh + deep_arg->ssh;
      }        /* end of 12-hour orbit, e >.5 section */
   else if( deep_arg->xnq < 1.2 * twopi / minutes_per_day &&
            deep_arg->xnq > 0.8 * twopi / minutes_per_day)
      {                        /* "if mean motion is .8 to 1.2 revs/day" */
      const double q22    =  1.7891679E-6;
      const double q31    =  2.1460748E-6;
      const double q33    =  2.2123015E-7;
      const double cosio_plus_1 = 1. + deep_arg->cosio;
      const double g200 = 1+deep_arg->eosq*(-2.5+0.8125*deep_arg->eosq);
      const double g300 = 1+deep_arg->eosq*(-6+6.60937*deep_arg->eosq);
      const double f311 = 0.9375*deep_arg->sinio*deep_arg->sinio*
             (1+3*deep_arg->cosio)-0.75*cosio_plus_1;
      const double g310 = 1+2*deep_arg->eosq;
      const double f220 = 0.75 * cosio_plus_1 * cosio_plus_1;
      const double f330 = 2.5 * f220 * cosio_plus_1;

      deep_arg->resonance_flag = deep_arg->synchronous_flag = 1;
      /* Synchronous resonance terms initialization */
      deep_arg->del1 = 3*deep_arg->xnq*deep_arg->xnq*aqnv*aqnv;
      deep_arg->del2 = 2*deep_arg->del1*f220*g200*q22;
      deep_arg->del3 = 3*deep_arg->del1*f330*g300*q33*aqnv;
      deep_arg->del1 *= f311*g310*q31*aqnv;
      deep_arg->xlamo = tle->xmo+tle->xnodeo+tle->omegao-deep_arg->thgr;
      bfact = deep_arg->xmdot + deep_arg->omgdot + deep_arg->xnodot - thdt;
      bfact = bfact+deep_arg->ssl+deep_arg->ssg+deep_arg->ssh;
      } /* End of geosych case */
   else              /* it's neither a high-e 12-hr orbit nor a geosynch: */
      deep_arg->resonance_flag = deep_arg->synchronous_flag = 0;

   if( deep_arg->resonance_flag)
      {
      deep_arg->xfact = bfact-deep_arg->xnq;

      /* Initialize integrator */
      deep_arg->xli = deep_arg->xlamo;
      deep_arg->xni = deep_arg->xnq;
      deep_arg->atime = 0;
      }
   /* End case dpinit: */
}

/* 'dpsec' is unavoidably confusing.  See https://projectpluto.com/dpsec.htm
for some commentary on what's going on here. */

static inline void compute_dpsec_derivs( const deep_arg_t *deep_arg,
                                           double *derivs)
{
   const double sin_li = sin( deep_arg->xli);
   const double cos_li = cos( deep_arg->xli);
   const double sin_2li = 2. * sin_li * cos_li;
   const double cos_2li = 2. * cos_li * cos_li - 1.;
   int i;

   derivs[0] = 0.;
                /* Dot terms calculated,  using a lot of trig add/subtract */
                /* identities to reduce the computational load... at the   */
                /* cost of making the code somewhat hard to follow:        */
   if( deep_arg->synchronous_flag )
      {
/*    const double fasx2 = 0.1313091 radians =   7.523456 degrees */
/*    const double fasx4 = 2.8843198 radians = 165.259351 degrees */
/*    const double fasx6 = 0.3744809 radians =  21.456173 degrees */
      const double c_fasx2 =  0.99139134268488593;
      const double s_fasx2 =  0.13093206501640101;
      const double c_2fasx4 =  0.87051638752972937;
      const double s_2fasx4 = -0.49213943048915526;
      const double c_3fasx6 =  0.43258117585763334;
      const double s_3fasx6 =  0.90159499016666422;
      const double sin_3li = sin_2li * cos_li + cos_2li * sin_li;
      const double cos_3li = cos_2li * cos_li - sin_2li * sin_li;
      double term1a = deep_arg->del1 * (sin_li  * c_fasx2  - cos_li  * s_fasx2);
      double term2a = deep_arg->del2 * (sin_2li * c_2fasx4 - cos_2li * s_2fasx4);
      double term3a = deep_arg->del3 * (sin_3li * c_3fasx6 - cos_3li * s_3fasx6);
      double term1b = deep_arg->del1 * (cos_li  * c_fasx2  + sin_li  * s_fasx2);
      double term2b = 2. * deep_arg->del2 * (cos_2li * c_2fasx4 + sin_2li * s_2fasx4);
      double term3b = 3. * deep_arg->del3 * (cos_3li * c_3fasx6 + sin_3li * s_3fasx6);

      for( i = 0; i < dpsec_integration_order; i += 2)
         {
         *derivs++ = term1a + term2a + term3a;
         *derivs++ = term1b + term2b + term3b;
         if( i + 2 < dpsec_integration_order)
            {
            term1a = -term1a;
            term2a *= -4.;
            term3a *= -9.;
            term1b = -term1b;
            term2b *= -4.;
            term3b *= -9.;
            }
         }
      }        /* end of geosynch case */
   else
      {        /* orbit is a 12-hour resonant one: */
/*    const double g22    =  5.7686396;      */
/*    const double g32    =  0.95240898;     */
/*    const double g44    =  1.8014998;      */
/*    const double g52    =  1.0508330;      */
/*    const double g54    =  4.4108898;      */
      const double c_g22 =  0.87051638752972937;
      const double s_g22 = -0.49213943048915526;
      const double c_g32 =  0.57972190187001149;
      const double s_g32 =  0.81481440616389245;
      const double c_g44 = -0.22866241528815548;
      const double s_g44 =  0.97350577801807991;
      const double c_g52 =  0.49684831179884198;
      const double s_g52 =  0.86783740128127729;
      const double c_g54 = -0.29695209575316894;
      const double s_g54 = -0.95489237761529999;
      const double xomi =
                deep_arg->omegaq + deep_arg->omgdot * deep_arg->atime;
      const double sin_omi = sin( xomi), cos_omi = cos( xomi);
      const double sin_li_m_omi = sin_li * cos_omi - sin_omi * cos_li;
      const double sin_li_p_omi = sin_li * cos_omi + sin_omi * cos_li;
      const double cos_li_m_omi = cos_li * cos_omi + sin_omi * sin_li;
      const double cos_li_p_omi = cos_li * cos_omi - sin_omi * sin_li;
      const double sin_2omi = 2. * sin_omi * cos_omi;
      const double cos_2omi = 2. * cos_omi * cos_omi - 1.;
      const double sin_2li_m_omi = sin_2li * cos_omi - sin_omi * cos_2li;
      const double sin_2li_p_omi = sin_2li * cos_omi + sin_omi * cos_2li;
      const double cos_2li_m_omi = cos_2li * cos_omi + sin_omi * sin_2li;
      const double cos_2li_p_omi = cos_2li * cos_omi - sin_omi * sin_2li;
      const double sin_2li_p_2omi = sin_2li * cos_2omi + sin_2omi * cos_2li;
      const double cos_2li_p_2omi = cos_2li * cos_2omi - sin_2omi * sin_2li;
      const double sin_2omi_p_li = sin_li * cos_2omi + sin_2omi * cos_li;
      const double cos_2omi_p_li = cos_li * cos_2omi - sin_2omi * sin_li;
      double term1a =
               deep_arg->d2201 * (sin_2omi_p_li*c_g22 - cos_2omi_p_li*s_g22)
             + deep_arg->d2211 * (sin_li * c_g22 - cos_li * s_g22)
             + deep_arg->d3210 * (sin_li_p_omi*c_g32 - cos_li_p_omi*s_g32)
             + deep_arg->d3222 * (sin_li_m_omi*c_g32 - cos_li_m_omi*s_g32)
             + deep_arg->d5220 * (sin_li_p_omi*c_g52 - cos_li_p_omi*s_g52)
             + deep_arg->d5232 * (sin_li_m_omi*c_g52 - cos_li_m_omi*s_g52);
      double term2a =
               deep_arg->d4410 * (sin_2li_p_2omi*c_g44 - cos_2li_p_2omi*s_g44)
             + deep_arg->d4422 * (sin_2li * c_g44 - cos_2li * s_g44)
             + deep_arg->d5421 * (sin_2li_p_omi*c_g54 - cos_2li_p_omi*s_g54)
             + deep_arg->d5433 * (sin_2li_m_omi*c_g54 - cos_2li_m_omi*s_g54);
      double term1b =
              (deep_arg->d2201 * (cos_2omi_p_li*c_g22 + sin_2omi_p_li*s_g22)
             + deep_arg->d2211 * (cos_li * c_g22 + sin_li * s_g22)
             + deep_arg->d3210 * (cos_li_p_omi*c_g32 + sin_li_p_omi*s_g32)
             + deep_arg->d3222 * (cos_li_m_omi*c_g32 + sin_li_m_omi*s_g32)
             + deep_arg->d5220 * (cos_li_p_omi*c_g52 + sin_li_p_omi*s_g52)
             + deep_arg->d5232 * (cos_li_m_omi*c_g52 + sin_li_m_omi*s_g52));
      double term2b = 2. *
              (deep_arg->d4410 * (cos_2li_p_2omi*c_g44 + sin_2li_p_2omi*s_g44)
             + deep_arg->d4422 * (cos_2li * c_g44 + sin_2li * s_g44)
             + deep_arg->d5421 * (cos_2li_p_omi*c_g54 + sin_2li_p_omi*s_g54)
             + deep_arg->d5433 * (cos_2li_m_omi*c_g54 + sin_2li_m_omi*s_g54));

      for( i = 0; i < dpsec_integration_order; i += 2)
         {
         *derivs++ = term1a + term2a;
         *derivs++ = term1b + term2b;
         if( i + 2 < dpsec_integration_order)
            {
            term1a = -term1a;
            term2a *= -4.;
            term1b = -term1b;
            term2b *= -4.;
            }
         }
      } /* End of 12-hr resonant case */
}

void Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg)
{
   double temp, xni, xli;
   int final_integration_step = 0;

   deep_arg->xll += deep_arg->ssl*deep_arg->t;
   deep_arg->omgadf += deep_arg->ssg*deep_arg->t;
   deep_arg->xnode += deep_arg->ssh*deep_arg->t;
   deep_arg->em = tle->eo+deep_arg->sse*deep_arg->t;
   deep_arg->xinc = tle->xincl+deep_arg->ssi*deep_arg->t;
   if( !deep_arg->resonance_flag ) return;

            /* If we're closer to t=0 than to the currently-stored data
               from the previous call to this function,  then we're
               better off "restarting",  going back to the initial data.
               The Dundee code rigs things up to _always_ take 720-minute
               steps from epoch to end time,  except for the final step.
               So if we'd have to integrate "backwards" (toward the epoch),
               we gotta do a restart if we're to be Dundee-compliant.  */
   if( fabs( deep_arg->t) < fabs( deep_arg->t - deep_arg->atime)
            || (is_dundee_compliant && fabs( deep_arg->t) < fabs( deep_arg->atime)))
      {                                    /* Epoch restart */
      deep_arg->atime = 0.;
      xni = deep_arg->xnq;
      xli = deep_arg->xlamo;
      }
   else                          /* use xni, xli from previous runs: */
      {
      xni = deep_arg->xni;
      xli = deep_arg->xli;
      }

   while( !final_integration_step)
      {
      double xldot, derivs[20], xlpow = 1., delt_factor;
      double delt = deep_arg->t - deep_arg->atime;
      int i;

      deep_arg->xni = xni;
      deep_arg->xli = xli;
      compute_dpsec_derivs( deep_arg, derivs);
      if( delt > dpsec_integration_step)
         delt = dpsec_integration_step;
      else if( delt < -dpsec_integration_step)
         delt = -dpsec_integration_step;
      else
         final_integration_step = 1;

      xldot = xni+deep_arg->xfact;

      xli += delt * xldot;
      xni += delt * derivs[0];
      delt_factor = delt;
      for( i = 2; i <= dpsec_integration_order; i++)
         {
         xlpow *= xldot;
         derivs[i - 1] *= xlpow;
         delt_factor *= delt / (double)i;
         xli += delt_factor * derivs[i - 2];
         xni += delt_factor * derivs[i - 1];
         }
      if( !is_dundee_compliant || !final_integration_step)
         {
         deep_arg->xni = xni;
         deep_arg->xli = xli;
         deep_arg->atime += delt;
         }
      }

   deep_arg->xn = xni;

   temp = -deep_arg->xnode + deep_arg->thgr + deep_arg->t * thdt;

   deep_arg->xll = xli + temp
         + (deep_arg->synchronous_flag ? -deep_arg->omgadf : temp);
   /*End case dpsec: */
}

void Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg)
{
   double sinis, cosis;

            /* If the time didn't change by more than 30 minutes,      */
            /* there's no good reason to recompute the perturbations;  */
            /* they don't change enough over so short a time span.     */
            /* However,  the Dundee code _always_ recomputes,  so if   */
            /* we're attempting to replicate its results,  we've gotta */
            /* recompute everything,  too.                             */
   if( fabs(deep_arg->savtsn-deep_arg->t) >= 30. || is_dundee_compliant)
      {
      double zf, zm, sinzf, ses, sis, sil, sel, sll, sls;
      double f2, f3, sghl, sghs, shs, sh1;

      deep_arg->savtsn = deep_arg->t;

            /* Update solar perturbations for time T: */
      zm = deep_arg->zmos+zns_per_min*deep_arg->t;
      zf = zm+2*zes*sin(zm);
      sinzf = sin(zf);
      f2 = 0.5*sinzf*sinzf-0.25;
      f3 = -0.5*sinzf*cos(zf);
      ses = deep_arg->se2*f2+deep_arg->se3*f3;
      sis = deep_arg->si2*f2+deep_arg->si3*f3;
      sls = deep_arg->sl2*f2+deep_arg->sl3*f3+deep_arg->sl4*sinzf;
      sghs = deep_arg->sgh2*f2+deep_arg->sgh3*f3+deep_arg->sgh4*sinzf;
      shs = deep_arg->sh2*f2+deep_arg->sh3*f3;

            /* Update lunar perturbations for time T: */
      zm = deep_arg->zmol+znl_per_min*deep_arg->t;
      zf = zm+2*zel*sin(zm);
      sinzf = sin(zf);
      f2 = 0.5*sinzf*sinzf-0.25;
      f3 = -0.5*sinzf*cos(zf);
      sel = deep_arg->ee2*f2+deep_arg->e3*f3;
      sil = deep_arg->xi2*f2+deep_arg->xi3*f3;
      sll = deep_arg->xl2*f2+deep_arg->xl3*f3+deep_arg->xl4*sinzf;
      sghl = deep_arg->xgh2*f2+deep_arg->xgh3*f3+deep_arg->xgh4*sinzf;
      sh1 = deep_arg->xh2*f2+deep_arg->xh3*f3;

            /* Sum the solar and lunar contributions: */
      deep_arg->pe = ses+sel;
      deep_arg->pinc = sis+sil;
      deep_arg->pl = sls+sll;
      deep_arg->pgh = sghs+sghl;
      deep_arg->ph = shs+sh1;
#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH
      if( deep_arg->solar_lunar_init_flag)
         {
         deep_arg->pe0 = deep_arg->pe;
         deep_arg->pinc0 = deep_arg->pinc;
         deep_arg->pl0 = deep_arg->pl;
         deep_arg->pgh0 = deep_arg->pgh;
         deep_arg->ph0 = deep_arg->ph;
         }
      deep_arg->pe  -= deep_arg->pe0;
      deep_arg->pinc -= deep_arg->pinc0;
      deep_arg->pl  -= deep_arg->pl0;
      deep_arg->pgh -= deep_arg->pgh0;
      deep_arg->ph  -= deep_arg->ph0;
      if( deep_arg->solar_lunar_init_flag)
         return;        /* done all we really need to do here... */
#endif
      }

               /* In Spacetrack 3, sinis & cosis were initialized       */
               /* _before_ perturbations were added to xinc.  In        */
               /* Spacetrack 6,  it's the other way around (see below). */
#ifndef SPACETRACK_3
   deep_arg->xinc += deep_arg->pinc;
#endif
   sinis = sin( deep_arg->xinc);
   cosis = cos( deep_arg->xinc);
#ifdef SPACETRACK_3
   deep_arg->xinc += deep_arg->pinc;
#endif

         /* Add solar/lunar perturbation correction to eccentricity: */
   deep_arg->em += deep_arg->pe;
   deep_arg->xll += deep_arg->pl;
   deep_arg->omgadf += deep_arg->pgh;
   if( tle->xincl >= 0.2)
      {             /* Apply periodics directly */
      double temp_val;

#ifdef SPACETRACK_3
      sinis = sin(deep_arg->xinc);
      cosis = cos(deep_arg->xinc);
#endif
      temp_val = deep_arg->ph / sinis;
      deep_arg->omgadf -= cosis * temp_val;
      deep_arg->xnode += temp_val;
      }
   else
      {
        /* Apply periodics with Lyddane modification */
      const double sinok = sin(deep_arg->xnode);
      const double cosok = cos(deep_arg->xnode);
      const double alfdp = deep_arg->ph * cosok
                    + (deep_arg->pinc * cosis + sinis) * sinok;
      const double betdp = - deep_arg->ph * sinok
                    + (deep_arg->pinc * cosis + sinis) * cosok;
      double dls, delta_xnode;

//    deep_arg->xnode = FMod2p(deep_arg->xnode);
      delta_xnode = atan2(alfdp,betdp) - deep_arg->xnode;

       /* This is a patch to Lyddane modification suggested */
       /* by Rob Matson, streamlined very slightly by BJG, to */
       /* keep 'delta_xnode' between +/- 180 degrees: */

      if( delta_xnode < - pi)
         delta_xnode += twopi;
      else if( delta_xnode > pi)
         delta_xnode -= twopi;

      dls = -deep_arg->xnode * sinis * deep_arg->pinc;
#ifdef SPACETRACK_3
      deep_arg->omgadf += dls
               + cosis * deep_arg->xnode -
               - cos( deep_arg->xinc) * (deep_arg->xnode + delta_xnode);
#else
      deep_arg->omgadf += dls - cosis * delta_xnode;
#endif
      deep_arg->xnode += delta_xnode;
      } /* End case dpper: */
}


================================================
FILE: dropouts.c
================================================
/* Code to check for the existence of certain artsats in Space-Track's
master TLE list.  Occasionally,  they've dropped objects and I didn't
realize it.  The objects ended up on NEOCP and I didn't ID them as
quickly as might be desired,  because I assumed they must be "new".
This should warn me if certain artsats get dropped from 'all_tle.txt'.

   The absence of certain artsats is essentially routine.  But for some
objects (marked with an !),  Space-Track is our only source of TLEs.
(Or at least,  I've been relying on them.  I _could_ generate TLES for
CXO,  for example,  based on _Horizons_ ephems.  Since I don't,  I want
this program to squawk loudly if Space-Track stops supplying CXO TLEs.)

   As of 2024 Aug 31,  the program can also be used for updating the
Space-Track TLEs in a slightly more cautious manner.  If you have
downloaded new TLEs as the (default) ALL_TLE.TXT,  and your "usual"
TLEs are at all_tle.txt,  then

./dropouts ALL_TLE.TXT 25000 all_tle.txt

   will check to see if ALL_TLE.TXT actually has 25000 or more TLEs in it.
If it does,  the download is presumed to have succeeded;  all_tle.txt is
unlinked and replaced with ALL_TLE.TXT.  If it fails,  we leave both files
undisturbed.

   This should help in the increasingly frequent situations where new
TLE files are downloaded and then have only an error message,  or a
drastically reduced number of TLEs.        */

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#ifdef _WIN32
   #include <io.h>
#else
   #include <unistd.h>
#endif

#define VT_NORMAL        "\033[0m"
#define VT_REVERSE       "\033[7m"

int main( const int argc, const char **argv)
{
   static const char *sats[] = {
          "00041A ! Cluster II-FM7",
          "00045A   Cluster II-FM5",
          "00045B ! Cluster II-FM8",
          "02048A ! INTEGRAL",
          "07004A ! THEMIS-A",
          "07004D ! THEMIS-D",
          "07004E ! THEMIS-E",
          "09017B ! Atlas 5 Centaur R/B",
          "09068B ! Delta 4 R/B",
          "12003B ! Delta 4 R/B",
          "12011B ! Breeze-M R/B",
          "13024B ! WGS-5 R/B",
          "13026B ! Breeze-M R/B",
          "15005B ! Inmarsat 5F2 booster",
          "15011A ! MMS 1",
          "15011B ! MMS 2",
          "15011C ! MMS 3",
          "15011D ! MMS 4",
          "15019C ! Yuanzheng-1 Y1",
          "15042B ! Breeze-M R/B",
          "16041A ! MUOS 5",
          "18038A   TESS",
          "22110B ! Ariane 5 R/B",
          "22146B ! Falcon 9 R/B",
          "22134B ! Falcon 9 R/B",
          "24048E   DRO R/B",
          "24059B ! Falcon 9 R/B",
          "24127B ! Falcon 9 R/B",
          "24233A ! Proba-3",
          "24233B ! Proba-3 booster",
          "63039A   Vela 1A",
          "64040B   Vela 2B",
          "65058A   Vela 3A",
          "65058B   Vela 6",
          "67040A   Vela 4A",
          "67040F ! Titan 3C transtage booster",
          "69046F ! Titan 3C transtage booster",
          "69046G   Vela 9/10 interstage",
          "70027C ! Vela 6 booster",
          "72073A   IMP-7",
          "76023C ! SOLRAD-11A",
          "76023H ! SOLRAD-11 debris",
          "77093E   SL-6 R/B(2)",
          "83020A ! ASTRON",
          "83020D ! ASTRON booster",
          "92044A   GEOTAIL",
          "95062A ! ISO",
          "95062C ! ISO debris",
          "97075B ! Equator S",
          "99040B ! Chandra X-Ray Observatory",
          "99040D ! IUS (for CXO)",
          "99066A ! XMM/Newton",
          "99066B ! XMM/Newton booster",
          NULL };
   FILE *ifile = fopen( argc == 1 ? "all_tle.txt" : argv[1], "rb");
   char buff[100];
   size_t i;
   int trouble_found = 0, n_found = 0;

   assert( ifile);
   while( fgets( buff, sizeof( buff), ifile))
      if( *buff == '1' && buff[1] == ' ' && buff[7] == 'U')
         {
         size_t len = strlen( buff);

         while( len && buff[len - 1] < ' ')
            len--;
         if( 69 == len)
            {
            n_found++;
            for( i = 0; sats[i]; i++)
               if( sats[i][0] == buff[9] && !memcmp( sats[i], buff + 9, 7))
                  sats[i] = "";
            }
         }
   fclose( ifile);
   printf( "This will list high-flying artsats for which TLEs are not provided :\n");
   for( i = 0; sats[i]; i++)
      if( sats[i][0])
         {
         printf( "%s\n", sats[i]);
         if( sats[i][7] == '!')
            {
            trouble_found = 1;
            printf( VT_REVERSE);
            printf( "DANGER!!! We do NOT have an independent source of TLEs\n");
            printf( "for this object.  Please report to "
                        "pluto\x40\x70roject\x70luto\x2e\x63om.\n");
                   /* Above is (slightly) obfuscated address to foil spambots */
            printf( "This needs to be fixed.\n");
            printf( VT_NORMAL);
            }
         }
   if( !trouble_found)
      printf( "Any missing objects are covered by other sources.  Nothing\n"
              "to worry about here.\n");
   printf( "%d found\n", n_found);
   if( 4 == argc && n_found > atoi( argv[2]))
      {
      printf( "Replacing '%s' with '%s'\n", argv[3], argv[1]);
#ifdef _WIN32
      _unlink( argv[3]);
#else
      unlink( argv[3]);
#endif
      rename( argv[1], argv[3]);
      }
   return( 0);
}


================================================
FILE: dynamic.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

/* Hooks to allow the satellite code to be accessed from a DLL,
with the DLL not loaded at startup;  instead,  LoadLibrary is
used at the time you decide you actually want satellite functions.
Not something likely to be useful to many people.  I used it some
years back for my desktop planetarium software;  I could check for
the existence of the DLL,  use it if available,  or fall back to
some built-in code if it wasn't.  (The DLL was,  by standards of
the day,  a little bit large.  Not everybody had enough interest
in artsats to download it.)   */

#include <stdio.h>
#include <stdlib.h>
#include "windows.h"
#include "norad.h"

typedef void (__stdcall *sxpx_init_fn)( double *params, const tle_t *tle);
typedef int (__stdcall *sxpx_fn)( const double tsince, const tle_t *tle,
                     const double *params, double *pos, double *vel);
typedef long (__stdcall *sxpx_version_fn)( void);

static HINSTANCE load_sat_code_lib( const int unload)
{
   static HINSTANCE h_sat_code_lib = (HINSTANCE)0;
   static int first_time = 1;

   if( unload)
      {
      if( h_sat_code_lib)
         FreeLibrary( h_sat_code_lib);
      h_sat_code_lib = NULL;
      first_time = 1;
      }
   else if( first_time)
      {
      h_sat_code_lib = LoadLibrary( "sat_code.dll");
      first_time = 0;
      }
   return( h_sat_code_lib);
}

/* 26 Nov 2002:  revised following two functions slightly so that the
   return values distinguish between "didn't get the function" and
   "didn't get the library" */

int SXPX_init( double *params, const tle_t *tle, const int sxpx_num)
{
   static sxpx_init_fn func[5];
   static char already_done[5];
   int rval = 0;
   HINSTANCE h_sat_code_lib;

   if( !params)         /* flag to unload library */
      {
      int i;

      load_sat_code_lib( -1);
      for( i = 0; i < 5; i++)
         already_done[i] = 0;
      return( 0);
      }
   h_sat_code_lib = load_sat_code_lib( 0);
   if( !already_done[sxpx_num])
      {
      if( h_sat_code_lib)
         func[sxpx_num] = (sxpx_init_fn)GetProcAddress( h_sat_code_lib,
                                (LPCSTR)( sxpx_num + 1));
      already_done[sxpx_num] = 1;
      }
   if( func[sxpx_num])
      (*func[sxpx_num])( params, tle);
   else
      rval = -1;
   if( !h_sat_code_lib)
      rval = -2;
   return( rval);
}

int SXPX( const double tsince, const tle_t *tle, const double *params,
                               double *pos, double *vel, const int sxpx_num)
{
   static sxpx_fn func[5];
   static char already_done[5];
   int rval = 0;
   HINSTANCE h_sat_code_lib = load_sat_code_lib( 0);

   if( !already_done[sxpx_num])
      {
      if( h_sat_code_lib)
         func[sxpx_num] = (sxpx_fn)GetProcAddress( h_sat_code_lib,
                                (LPCSTR)( sxpx_num + 6));
      already_done[sxpx_num] = 1;
      }
   if( func[sxpx_num])
      rval = (*func[sxpx_num])( tsince, tle, params, pos, vel);
   else
      rval = -1;
   if( !h_sat_code_lib)
      rval = -2;
   return( rval);
}

long get_sat_code_lib_version( void)
{
   HINSTANCE h_sat_code_lib = load_sat_code_lib( 0);
   long rval;

   if( !h_sat_code_lib)
      rval = -2;
   else
      {
      sxpx_version_fn func =
               (sxpx_version_fn)GetProcAddress( h_sat_code_lib, (LPCSTR)20);

      if( !func)
         rval = -1;
      else
         rval = (*func)( );
      }
   return( rval);
}


================================================
FILE: elem2tle.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

/* MOSTLY OBSOLETE.  See 'eph2tle.cpp' in the Find_Orb project
(https://github.com/Bill-Gray/find_orb) for a considerably better
approach to computing TLEs from orbital data.

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "watdefs.h"
#include "afuncs.h"
#include "comets.h"
#include "norad.h"
#include "norad_in.h"   /* for xke definition */
#include "date.h"

const double earth_mass_over_sun_mass = 2.98994e-6;
#define GAUSS_K .01720209895
#define SOLAR_GM (GAUSS_K * GAUSS_K)
#define PI 3.141592653589793238462643383279502884197169399375105

int write_tle_from_vector( char *buff, const double *state_vect,
        const double epoch, const char *norad_desig, const char *intl_desig);

int verbose = 0;

static void set_tle_defaults( tle_t *tle)
{
   memset( tle, 0, sizeof( tle_t));
   strcpy( tle->intl_desig, "56999ZZ ");
   tle->classification = 'U';
   tle->ephemeris_type = '0';
}

#define centralize_angle(x) (fmod( (x) + PI * 10., PI + PI))

int vector_to_tle( tle_t *tle, const double *state_vect)
{
   ELEMENTS elem;
   int rval = 0, i;
   double tvect[6];
   const double max_ecc = .9999;

   for( i = 0; i < 6; i++)      /* cvt from km, km/min to AU, AU/day */
      tvect[i] = state_vect[i];
   elem.gm = xke * xke * earth_radius_in_km * earth_radius_in_km * earth_radius_in_km;
   calc_classical_elements( &elem, tvect, tle->epoch, 1);
   tle->xincl = centralize_angle( elem.incl);
   tle->xnodeo = centralize_angle( elem.asc_node);
   tle->omegao = centralize_angle( elem.arg_per);
   tle->xmo = centralize_angle( elem.mean_anomaly);

   if( elem.ecc > max_ecc || elem.major_axis <= 0.)
      rval = -1;
   else
      {
      tle->eo = elem.ecc;
      tle->xno = 1. / elem.t0;      /* xno is now in radians per minute */
      rval = 0;
      }
   if( tle->xincl < 0.)
      {
      tle->xincl = -tle->xincl;
      tle->xnodeo = centralize_angle( tle->xnodeo + PI);;
      tle->omegao = centralize_angle( tle->omegao + PI);;
      }
   return( rval);
}

static void show_results( const char *title, const tle_t *tle, const double *state_vect)
{
   if( title)
      printf( "%s\n", title);
   if( tle)
      {
      char buff[200];

      write_elements_in_tle_format( buff, tle);
      printf( "%s", buff);
      }
   printf("    %16.8f %16.8f %16.8f \n", state_vect[0], state_vect[1],
                                         state_vect[2]);
   printf("    %16.8f %16.8f %16.8f \n", state_vect[3] / 60.,
                                                state_vect[4] / 60.,
                                                state_vect[5] / 60.);
}

static int compute_new_state_vect( const tle_t *tle, double *state_vect,
                     const int ephem)
{
   double sat_params[N_SAT_PARAMS];
   int rval = 0;

   switch( ephem)
      {
      case 0:
         SGP_init( sat_params, tle);
         rval = SGP( 0., tle, sat_params, state_vect, state_vect + 3);
         break;
      case 1:
         SGP4_init( sat_params, tle);
         rval = SGP4( 0., tle, sat_params, state_vect, state_vect + 3);
         break;
      case 2:
         SGP8_init( sat_params, tle);
         rval = SGP8( 0., tle, sat_params, state_vect, state_vect + 3);
         break;
      case 3:
         SDP4_init( sat_params, tle);
         rval = SDP4( 0., tle, sat_params, state_vect, state_vect + 3);
         break;
      case 4:
         SDP8_init( sat_params, tle);
         rval = SDP8( 0., tle, sat_params, state_vect, state_vect + 3);
         break;
      default:
         printf( "??? ephem = %d\n", ephem);
         rval = -99;
         break;
      }
// if( rval)
//    printf( "??? rval = %d; ecc = %.6lf\n", rval, tle->eo);
   return( rval);
}

#define SIMPLEX_POINT struct simplex_point

SIMPLEX_POINT
   {
   double state_vect[6];
   double error;
   };

static double total_vector_diff( const double *vect1, const double *vect2)
{
   int i;
   double rval = 0.;

   for( i = 0; i < 6; i++)
      {
      double delta = vect1[i] - vect2[i];
      if( i >= 3)
         delta *= 1000.;
      rval += delta * delta;
      }
   return( rval);
}

static double compute_simplex_point_error( const double *state_vect, tle_t *tle,
            const double *start, const int ephem)
{
   double rval = 0., state_out[6];
   int compute_rval, vect_to_tle_rval;

   vect_to_tle_rval = vector_to_tle( tle, state_vect);
   if( vect_to_tle_rval == -1)
      return( 1.e+37);
   compute_rval = compute_new_state_vect( tle, state_out, ephem);
   if( compute_rval == SXPX_ERR_NEARLY_PARABOLIC
         || compute_rval == SXPX_ERR_NEGATIVE_MAJOR_AXIS
         || compute_rval == SXPX_ERR_NEGATIVE_XN
         || vect_to_tle_rval == -1)
      rval = 1.e+37;       /* invalid vector */
   else
      rval = total_vector_diff( state_out, start);
   return( rval);
}

static double try_simplex( SIMPLEX_POINT *simp, const double factor,
               tle_t *tle, const double *start, const int ephem)
{
   SIMPLEX_POINT new_point;
   int i, j;

   for( i = 0; i < 6; i++)
      {
      new_point.state_vect[i] = factor * simp->state_vect[i];
      for( j = 1; j < 7; j++)
         new_point.state_vect[i] += (1. - factor) * simp[j].state_vect[i] / 6.;
      }
   new_point.error = compute_simplex_point_error( new_point.state_vect, tle,
                                           start, ephem);
   if( new_point.error <= simp->error)
      *simp = new_point;
   return( new_point.error);
}

static void sort_simplexes( SIMPLEX_POINT *simp)
{
   int i;

   for( i = 0; i < 7; i++)          /* sort simplex points by error */
      if( simp[i].error < simp[i + 1].error)   /* highest to lowest */
         {
         SIMPLEX_POINT temp_elem = simp[i];

         simp[i] = simp[i + 1];
         simp[i + 1] = temp_elem;
         if( i)
            i -= 2;
         }
}

double dist_offset = 10000., vel_offset = 10.;

static void create_randomized_simplex( SIMPLEX_POINT *simp, const double *start_vect)
{
   int i;

   for( i = 0; i < 6; i++)
      {
      const double zval = (double)rand( ) / (double)RAND_MAX - .5;
      simp->state_vect[i] = start_vect[i]
                        + zval * (i < 3 ? dist_offset : vel_offset);
      }
}

static int initialize_simplexes( SIMPLEX_POINT *simp, const double *state_vect,
                                   const double *start_vect, const int ephem)
{
   int i, rval = 0;

   memcpy( simp[6].state_vect, start_vect, 6 * sizeof( double));
   assert( start_vect[0] && start_vect[1] && start_vect[2]);
   for( i = 0; i < 7 && !rval; i++)
      {
      tle_t tle;
      int iter = 0;

      set_tle_defaults( &tle);
      if( i != 6)
         create_randomized_simplex( simp + i, start_vect);
      while ( (simp[i].error = compute_simplex_point_error( simp[i].state_vect,
                          &tle, state_vect, ephem)) > 1e+36 && iter++ < 1000)
         create_randomized_simplex( simp, start_vect);
      if( iter >= 1000)
         rval = -1;
      }
   return( rval);
}

static int find_tle_via_simplex_method( tle_t *tle, const double *state_vect,
                     const double *start_vect, const int ephem)
{
   SIMPLEX_POINT simp[7];
   double best_rval_found = 1e+39, best_vect[6];
   int i, j, soln_found = 0, n_iterations = 0;
   int n_consecutive_contractions = 0;
   const int max_iterations = 43000;

   if( verbose)
      show_results( "Setting up:", NULL, start_vect);
   srand( 1);
   if( initialize_simplexes( simp, state_vect, start_vect, ephem))
      return( 0);       /* no solution found */
   while( !soln_found && n_iterations++ < max_iterations)
      {
      double ytry;

      sort_simplexes( simp);
      ytry = try_simplex( simp, -1., tle, state_vect, ephem);
      if( ytry <= simp[6].error)
         {
         if( verbose)
            {
            char buff[200];
            printf( "New record low: %f\n", ytry);

            write_elements_in_tle_format( buff, tle);
            printf( "%s", buff);
            }
         try_simplex( simp, 2., tle, state_vect, ephem);
         if( ytry < 1e-13)
            soln_found = true;
         if( ytry < best_rval_found)
            {
            best_rval_found = ytry;
            memcpy( best_vect, simp[0].state_vect, 6 * sizeof( double));
            }
         n_consecutive_contractions = 0;
         }
      else if( ytry > simp[1].error)
         {
         double ysave = simp[0].error;

         ytry = try_simplex( simp, .5, tle, state_vect, ephem);
         if( ytry > ysave)       /* still no success;  try contracting */
            {                    /* around lowest point: */
//          printf( "Contracting around best point\n");
            for( i = 0; i < 6; i++)
               {
               for( j = 0; j < 6; j++)
                  simp[i].state_vect[j] =
                           (simp[i].state_vect[j] + simp[6].state_vect[j]) / 2.;
               simp[i].error = compute_simplex_point_error( simp[i].state_vect, tle,
                                                               state_vect, ephem);
               }
            n_consecutive_contractions++;
            if( n_consecutive_contractions == 30)
               initialize_simplexes( simp, state_vect, best_vect, ephem);
            }
         else
            n_consecutive_contractions = 0;
         }
      if( n_iterations % 200 == 199)
         initialize_simplexes( simp, state_vect, best_vect, ephem);
      }
   sort_simplexes( simp);
   if( verbose)
      printf( "End err: %f\n", simp[6].error);
   vector_to_tle( tle, best_vect);
// vector_to_tle( tle, simp[6].state_vect);
   return( soln_found);
}

int compute_tle_from_state_vector( tle_t *tle, const double *state_vect, const int ephem,
                        double *trial_state)
{
   int n_failed_steps = 0, i;
   double state_out[6], best_vect[6], curr_err;
   const double thresh = 1e-12;

   memcpy( trial_state, state_vect, 6 * sizeof( double));
   if( vector_to_tle( tle, state_vect))
      {
      printf( "Immediate failure\n");
      return( -1);
      }
   memcpy( best_vect, state_vect, 6 * sizeof( double));
   compute_new_state_vect( tle, state_out, ephem);
   for( i = 0; i < 6; i++)
      trial_state[i] += state_vect[i] - state_out[i];
   curr_err = total_vector_diff( state_out, state_vect);
   if( verbose)
      show_results( "Initial guess", tle, state_out);
   if( curr_err < thresh)
      printf( "Got it right away\n");
   while( curr_err > thresh && n_failed_steps < 20)
      {
      double new_err = 0.;
      tle_t new_tle = *tle;

      if( vector_to_tle( &new_tle, trial_state))
         {
         memcpy( trial_state, best_vect, 6 * sizeof( double));
         show_results( "Simple failure:", tle, trial_state);
         return( -1);
         }
      compute_new_state_vect( &new_tle, state_out, ephem);
      new_err = total_vector_diff( state_out, state_vect);
      if( new_err > curr_err * .9)
          n_failed_steps++;      /* slow or no convergence */
      if( new_err < curr_err)
         {
         curr_err = new_err;
         *tle = new_tle;
         memcpy( best_vect, trial_state, 6 * sizeof( double));
         if( verbose)
            {
            printf( "New record %f\n", curr_err);
            show_results( NULL, tle, state_out);
            }
         }
      for( i = 0; i < 6; i++)
          trial_state[i] += state_vect[i] - state_out[i];
      }
   memcpy( trial_state, best_vect, 6 * sizeof( double));
   return( curr_err > thresh);
}

/* Main program */
int main( const int argc, const char **argv)
{
   const char *tle_filename = ((argc == 1) ? "test.tle" : argv[1]);
   FILE *ifile = fopen( tle_filename, "rb");
   tle_t tle; /* Pointer to two-line elements set for satellite */
   char line1[100], line2[100];
   int ephem = 1;       /* default to SGP4 */
   int i;               /* Index for loops etc */
   int n_failures = 0, n_simple = 0, n_simplex = 0;
   bool failures_only = false;

   for( i = 2; i < argc; i++)
      if( argv[i][0] == '-')
         switch( argv[i][1])
            {
            case 'f':
               failures_only = true;
               break;
            case 'v':
               verbose = 1;
               break;
            case 'd':
               dist_offset = atof( argv[i] + 2);
               break;
            case 's':
               vel_offset = atof( argv[i] + 2);
               break;
            default:
               printf( "Option '%s' unrecognized\n", argv[i]);
               break;
            }
   if( !ifile)
      {
      printf( "Couldn't open input TLE file %s\n", tle_filename);
      exit( -1);
      }
   *line1 = '\0';
   while( fgets( line2, sizeof( line2), ifile))
      {
      int got_data = 0;
      double state_vect[6];

      set_tle_defaults( &tle);
      if( strlen( line2) > 110 && line2[7] == '.' && line2[18] == '.'
                     && line2[0] == '2' && line2[1] == '4')
         {
         got_data = 3;           /* Find_Orb state vector ephemeris */
         tle.epoch = atof( line2);
         sscanf( line2 + 13, "%lf %lf %lf %lf %lf %lf",
                    state_vect + 0, state_vect + 1, state_vect + 2,
                    state_vect + 3, state_vect + 4, state_vect + 5);
         }
      else if( strlen( line1) > 55 && !memcmp( line1 + 50, " (TDB)", 6))
         {                                  /* JPL Horizons vector */
         const double obliq_2000 = 23.4392911 * PI / 180.;

         tle.epoch = atof( line1);          /* get JD epoch from header... */
         strcpy( line1, line2);
         if( fgets( line2, sizeof( line2), ifile))
            got_data = 1;
         sscanf( line1, "%lf %lf %lf",
                    state_vect + 0, state_vect + 1, state_vect + 2);
         sscanf( line2, "%lf %lf %lf",
                    state_vect + 3, state_vect + 4, state_vect + 5);
                      /* Cvt ecliptic to equatorial 2000: */
         rotate_vector( state_vect    , obliq_2000, 0);
         rotate_vector( state_vect + 3, obliq_2000, 0);
         }
      else if( parse_elements( line1, line2, &tle) >= 0)
         got_data = 2;

      if( got_data == 1 || got_data == 3)
         tle.epoch -= 68.00 / 86400.;       /* rough convert TDT to UTC */

      if( got_data)     /* hey! we got a TLE! */
         {
         double sat_params[N_SAT_PARAMS],  trial_state[6];
         int simple_rval;
         bool failed = false;
         tle_t new_tle;

         if( got_data == 1 || got_data == 3)
            {
            ephem = 3;        /* Use SDP4 for JPL Horizons vectors */
            for( i = 0; i < 6 && fabs( state_vect[i]) < 1.; i++)
               ;
            if( i == 6)   /* all small quantities,  must be in AU & AU/day : */
               {
               for( i = 0; i < 6; i++)
                  state_vect[i] *= AU_IN_KM;
               for( i = 3; i < 6; i++)
                  state_vect[i] /= seconds_per_day;
               }
            for( i = 3; i < 6; i++)    /* cvt km/sec to km/min */
               state_vect[i] *= seconds_per_minute;
            if( !failures_only)
               show_results( "Before:", NULL, state_vect);
            }
         else
            {
            int is_deep = select_ephemeris( &tle);

            if( is_deep && (ephem == 1 || ephem == 2))
               ephem += 2;    /* switch to an SDx */
            if( !is_deep && (ephem == 3 || ephem == 4))
               ephem -= 2;    /* switch to an SGx */

            /* Calling of NORAD routines */
            /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8)   */
            /* will be called in turn with the appropriate TLE set */
            switch( ephem)
               {
               case 0:
                  SGP_init( sat_params, &tle);
                  SGP( 0., &tle, sat_params, state_vect, state_vect + 3);
                  break;
               case 1:
                  SGP4_init( sat_params, &tle);
                  SGP4( 0., &tle, sat_params, state_vect, state_vect + 3);
                  break;
               case 2:
                  SGP8_init( sat_params, &tle);
                  SGP8( 0., &tle, sat_params, state_vect, state_vect + 3);
                  break;
               case 3:
                  SDP4_init( sat_params, &tle);
                  SDP4( 0., &tle, sat_params, state_vect, state_vect + 3);
                  break;
               case 4:
                  SDP8_init( sat_params, &tle);
                  SDP8( 0., &tle, sat_params, state_vect, state_vect + 3);
                  break;
               }
            if( !failures_only)
               show_results( "Before:", &tle, state_vect);
            }

         new_tle = tle;
         simple_rval = compute_tle_from_state_vector( &new_tle, state_vect, ephem, trial_state);
         if( simple_rval)
            {
            n_simplex++;
            find_tle_via_simplex_method( &new_tle, state_vect, trial_state, ephem);
            }
         else
            n_simple++;

         compute_new_state_vect( &new_tle, trial_state, ephem);
         for( i = 0; i < 6; i++)
            {
            trial_state[i] -= state_vect[i];
            if( fabs( trial_state[i]) > 1e-6)
               failed = true;
            }
         if( failed && failures_only)
            show_results( "Before:", &tle, state_vect);
         if( failed || !failures_only)
            show_results( (simple_rval ? "Simplex result:" : "Simplest method:"),
                                &new_tle, trial_state);
         if( failed)
            n_failures++;
         }
      strcpy( line1, line2);
      }
   fclose( ifile);
   printf( "%d solved with simple method; %d with simplex\n", n_simple, n_simplex);
   if( n_failures)
      printf( "%d failures\n", n_failures);
   return(0);
} /* End of main() */




================================================
FILE: fake_ast.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.

   This program will generate simulated geocentric observations
for a given object from a TLE.  In theory,  one can then fit these
pseudo-observations to a higher-quality physical model to get a
considerably more accurate ephemeris for the object.  */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "norad.h"
#include "observe.h"

#define PI 3.1415926535897932384626433832795028841971693993751058209749445923

int main( const int argc, const char **argv)
{
   FILE *ifile = fopen( argv[1], "rb");
   char line1[100], line2[100];
   const char *intl_id = NULL;
   double step_size = .1;
   int i, n_steps = 100;
   bool show_vectors = false;

   if( !ifile)
      {
      printf( "Couldn't open input file\n");
      exit( -1);
      }
   for( i = 1; i < argc; i++)
      if( argv[i][0] == '-')
         switch( argv[i][1])
            {
            case 'i':
               intl_id = argv[i] + 2;
               break;
            case 'n':
               n_steps = atoi( argv[i] + 2);
               break;
            case 's':
               step_size = atof( argv[i] + 2);
               break;
            case 'v':
               show_vectors = true;
               break;
            default:
               printf( "Unrecognized option '%s'\n", argv[i]);
               break;
            }
   *line1 = '\0';
   sxpx_set_implementation_param( SXPX_DUNDEE_COMPLIANCE, 1);
   while( fgets( line2, sizeof( line2), ifile))
      {
      tle_t tle; /* Pointer to two-line elements set for satellite */
      int err_val;

      if( (!intl_id || !memcmp( intl_id, line1 + 9, 6))
                && (err_val = parse_elements( line1, line2, &tle)) >= 0)
         {                  /* hey! we got a TLE! */
         int is_deep = select_ephemeris( &tle);
         double sat_params[N_SAT_PARAMS], observer_loc[3];
         double prev_pos[3];

         if( err_val)
            printf( "WARNING: TLE parsing error %d\n", err_val);
         for( i = 0; i < 3; i++)
            observer_loc[i] = 0.;
         if( is_deep)
            SDP4_init( sat_params, &tle);
         else
            SGP4_init( sat_params, &tle);
         for( i = 0; i < n_steps; i++)
            {
            double pos[3]; /* Satellite position vector */
            double t_since = (double)( i - n_steps / 2) * step_size;
            double jd = tle.epoch + t_since;

            t_since *= 1440.;
            if( is_deep)
               err_val = SDP4( t_since, &tle, sat_params, pos, NULL);
            else
               err_val = SGP4( t_since, &tle, sat_params, pos, NULL);
            if( err_val)
               printf( "Ephemeris error %d\n", err_val);
            if( show_vectors)
               {
               if( i)
                  printf( "%14.6f %14.6f %14.6f - ", pos[0] - prev_pos[0],
                                                 pos[1] - prev_pos[1],
                                                 pos[2] - prev_pos[2]);
               printf( "%14.6f %14.6f %14.6f\n", pos[0], pos[1], pos[2]);
               memcpy( prev_pos, pos, 3 * sizeof( double));
               }
            else
               {
               double ra, dec, dist_to_satellite;

               get_satellite_ra_dec_delta( observer_loc, pos,
                                 &ra, &dec, &dist_to_satellite);
               epoch_of_date_to_j2000( jd, &ra, &dec);
               printf( "%-14sC%13.5f    %08.4f    %+08.4f",
                     intl_id, jd, ra * 180. / PI, dec * 180. / PI);
               printf( "                    TLEs 500\n");
               }
            }
         }
      strcpy( line1, line2);
      }
   fclose( ifile);
   return( 0);
} /* End of main() */



================================================
FILE: fix_tles.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.

fix_tles : change TLE data and replace with correct checksums

   If one alters a TLE,  one will normally cause a change in
the checksum data at the end of the line.  This program reads
in successive lines from an input file;  if a line and the
preceding line make a TLE,  the checksum is computed for
both lines and is suitably reset.

   I've had instances where I realized that either the COSPAR
or NORAD designation was incorrectly set,  and added options
that let you specify which designation should be used for all
TLEs in the output.  (This is specific to my use case : I usually
compute many TLEs for a particular object,  which each TLE
being fitted to give good state vectors over one day.)

   At some point,  I may need to similarly batch-correct bulletin
numbers or something of that ilk,  but at present,  only the
designations (and checksums) can be batch-corrected with this
program.                                  */

#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "norad.h"

/* After being modified,  the checksum byte in TLEs must be recomputed : */

static void set_checksum( char *line)
{
   const int csum =  tle_checksum( line);

   assert( csum >= 0);
   line[68] += csum;
   if( line[68] > '9')
      line[68] -= 10;
}

static void usage( void)
{
   fprintf( stderr,
       "'fix_tles' reads in TLEs and outputs TLEs with corrected checksums.\n"
       "Options are :\n"
       "   -i YYNNNA     Replace COSPAR designation\n"
       "   -n NNNNN      Replace five-digit NORAD designation\n"
       "   -f filename   Specify input file (default = stdin)\n"
       "   -o filename   Specify output file (default = stdout)\n" );
   exit( -1);
}

int main( const int argc, const char **argv)
{
   int i, line_no = 0;
   const char *norad_desig = NULL;
   char intl_desig[10];
   FILE *ifile = stdin, *ofile = stdout;
   char line1[200], line2[200];

   *intl_desig = '\0';
   for( i = 1; i < argc; i++)
      if( argv[i][0] == '-')
         {
         const char *arg = argv[i] + 2;

         if( !*arg && i < argc - 1)
            arg = argv[i + 1];
         switch( argv[i][1])
            {
            case 'i':
               assert( strlen( arg) < 9);
               snprintf( intl_desig, sizeof( intl_desig), "%-9s", arg);
               break;
            case 'n':
               norad_desig = arg;
               break;
            case 'o':
               ofile = fopen( arg, "wb");
               if( !ofile)
                  {
                  fprintf( stderr, "'%s' not opened\n", arg);
                  usage( );
                  }
               break;
            case 'f':
               ifile = fopen( arg, "rb");
               if( !ifile)
                  {
                  fprintf( stderr, "'%s' not opened\n", arg);
                  usage( );
                  }
               break;
            default:
               fprintf( stderr, "Unrecognized option '%s'\n", argv[i]);
               usage( );
               break;
            }
         }
   *line1 = '\0';
   while( fgets( line2, sizeof( line2), ifile))
      {
      tle_t unused_tle;

      if( parse_elements( line1, line2, &unused_tle) >= 0)
         {
         if( norad_desig)
            {
            memcpy( line1 + 2, norad_desig, 5);
            memcpy( line2 + 2, norad_desig, 5);
            }
         if( *intl_desig)
            memcpy( line1 + 9, intl_desig, 8);
         set_checksum( line1);
         set_checksum( line2);
         }
      if( line_no)
         fputs( line1, ofile);
      line_no++;
      strcpy( line1, line2);
      }
   if( line_no)
      fputs( line1, ofile);
   return( 0);
}


================================================
FILE: get_el.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include "norad.h"

#define PI 3.141592653589793238462643383279502884197
#define TWOPI (2. * PI)
#define MINUTES_PER_DAY 1440.
#define MINUTES_PER_DAY_SQUARED (MINUTES_PER_DAY * MINUTES_PER_DAY)
#define MINUTES_PER_DAY_CUBED (MINUTES_PER_DAY * MINUTES_PER_DAY_SQUARED)
#define AE 1.0
                             /* distance units, earth radii */

/* TLEs have four angles on line 2,  given in the form DDD.DDDD.  This
can be parsed more quickly as an integer,  then cast to double and
converted to radians,  all in one step.    */

static int get_angle( const char *buff)
{
   int rval = 0;

   while( *buff == ' ')
      buff++;
   while( *buff != ' ')
      {
      if( *buff != '.')
         rval = rval * 10 + (int)( *buff - '0');
      buff++;
      }
   return( rval);
}

/* Converts the quasi scientific notation of the "Motion Dot Dot/6" or
"BSTAR" field to double.  The input will always be of the form

sdddddSe

   ....where s is blank or + or -;  ddddd is a five-digit mantissa;
S is + or - or blank;  and e is a single-digit exponent.  A decimal
point is assumed before the five-digit mantissa.  */

static double sci( const char *string)
{
   double rval = 0.;

   if( string[1] != ' ')
      {
      const int ival = atoi( string);

      if( ival)
         {
         rval = (double)ival * 1.e-5;
         if( string[7] != '0')
            {
            int exponent = string[7] - '0';

            if( string[6] == '-')
               while( exponent--)
                  rval *= .1;
            else
               while( exponent--)
                  rval *= 10.;
            }
         }
      }
   return( rval);
}


/* Does a checksum modulo 10 on the given line.  Digits = their
value, '-' = 1, all other chars = 0.  Returns 0 if ok, a negative
value if it's definitely not a TLE line,  positive if it's all OK
except the checksum.  This last was added because people sometimes
want to use TLEs without worrying about the checksum. */

int DLL_FUNC tle_checksum( const char *buff)
{
   int rval = 0;
   int count = 69;

   if( (*buff != '1' && *buff != '2') || buff[1] != ' ')
      return( -1);
   while( --count)
      {
      if( *buff > '0' && *buff <= '9')
         rval += *buff - '0';
      else if( *buff == '-')
         rval++;
      if( *buff < ' ' || *buff > 'z')           /* invalid character */
         return( -2);
      buff++;
      }
   rval -= *buff++ - '0';
   if( *buff > ' ')                 /* line unterminated */
      rval = -3;
   else
      {
      rval %= 10;
      if( rval < 0)
         rval += 10;
      }
   return( rval);
}

static inline int mutant_dehex( const char ichar)
{
   int rval;

   if( ichar <= '9' && ichar >= '0')
      rval = ichar - '0';
   else if( ichar >= 'A' && ichar <= 'Z')
      rval = ichar + 10 - 'A';
   else
      rval = -1;
   return( rval);
}

/* The "standard" SDP4 model fails badly for very high-flying satellites
(mostly,  but not always,  those with orbital periods of greater than
about a week).  Highly eccentric orbits are more likely to fail than
near-circular ones.  And of course,  hyperbolic orbits never work with
SGP4/SDP4.

   As a non-standard extension,  I'm simply storing state vectors for
such orbits,  using the following somewhat odd scheme :

1 40391U 15007B   15091.99922241 sxxxxxxxx syyyyyyyy szzzzzzzzH  9997
2 49391 [valid range, accuracy]  saaaaaaaa sbbbbbbbb scccccccc    0 8

   Epoch,  int'l & NORAD IDs are stored in the standard manner.  The
'ephemeris type' is H (rather than the otherwise universal 0).  The
xyz position and vx, vy, vz velocity are stored as 8-digit signed
base-36 integers,  hence a range of +/- 36^8 = about +/- 2.82x10^12.

  x, y, z are in meters,  and hence cover a range +/- 18.9 AU.
vx, vy, vz are in 10^-4 m/s,  range +/- 94% c.  The state vectors
are in the geocentric ecliptic plane of date.  See 'sdp4.cpp' for
a discussion of how they're actually used.  */

static double get_high_value( const char *iptr)
{
   int64_t rval = 0;

   assert( *iptr == '+' || *iptr == '-');
   if( *iptr == '+' || *iptr == '-')
      {
      int i, digit;

      for( i = 1; i < 9; i++)
         {
         digit = mutant_dehex( iptr[i]);
         assert( digit >= 0);
         rval = rval * (int64_t)36 + (int64_t)digit;
         }
      if( *iptr == '-')
         rval = -rval;
      }
   return( (double)rval);
}

/* Traditionally,  NORAD numbers were stored as five digits.  In 2020, new
detectors threatened to go past 100K objects;  the 'Alpha-5' scheme allows
the first byte to be replaced by an uppercase letter,  with I and O
skipped.  That gets us to 339999 :

https://www.space-track.org/documentation#tle-alpha5

   Note that Alpha-5 is referred to as a "stopgap".  Near the bottom of
the above link,  "space-track.org encourages users to switch to... XML,
KVN,  or JSON",  (partly) because these will handle nine-digit catalog
numbers.

   To go beyond the Alpha-5 limit of 340000 possible numbers and store
all nine-digit numbers in five bytes,  I have added options 3 and 4
below.  To do so,  we need a 'base64'-like scheme,  using all ten
digits,  26 uppercase and 26 lowercase letters,  and + and /.

   d = digit, L = uppercase letter,  x = any base64 character
   X = non-digit base-64 character

(1) ddddd = 'traditional' scheme provides 100000 combinations;
         Numbers 0 to 99999

(2) Ldddd = Alpha-5 scheme adds 240000
         Numbers 100000 to 339999;     A0000 to Z9999

(3) xxxxX = 64^4*54      = 905969664 more  (start of 'Super-5' range)
         Numbers 340000 to 906309663;  0000A to -----

(4) xxxXd = 64^3*54*10   = 141557760 more
         Numbers 906309664 to 1047867423;  000A0 and up
              (going slightly past the billion we actually need) */

static int base64_to_int( const char c)
{
   int offset;

   if( c >= 'A')
      {
      if( c <= 'Z')
         offset = 'A' - 10;
      else if( c >= 'a' && c <= 'z')
         offset = 'a' - 10 - 26;
      else
         return( -1);
      }
   else
      {
      if( c >= '0' && c <= '9')
         offset = '0';
      else if( c == ' ')
         return( 0);
      else if( c == '+')
         return( 62);
      else if( c == '-')
         return( 63);
      else
         return( -1);
      }
   return( c - offset);
}

static int get_norad_number( const char *buff)
{
   size_t i;
   int digits[5], rval = 0;

   for( i = 0; i < 5; i++)
      {
      digits[i] = base64_to_int( buff[i]);
      if( digits[i] == -1)       /* not a valid number */
         return( 0);
      }
   if( digits[4] > 9)      /* case (3): last char is uppercase */
      rval = 340000 + (digits[4] - 10)
            + 54 * (digits[3] + (digits[2] << 6) + (digits[1] << 12) + (digits[0] << 18));
   else if( digits[3] > 9)    /* case (4) above */
      rval = 340000 + 905969664 + digits[4] + (digits[3] - 10) * 10
            + 540 * (digits[2] + (digits[1] << 6) + (digits[0] << 12));
   else        /* last four digits are 0-9;  'standard' NORAD desig */
      {
      for( i = 1; i <= 4; i++)
         assert( (buff[i] >= '0' && buff[i] <= '9') || buff[i] == ' ');
      if( *buff > 'I')
         {
         digits[0]--;
         if( *buff > 'O')
            digits[0]--;
         }
      rval = digits[0] * 10000 + atoi( buff + 1);
      }
   return( rval);
}

static inline double get_eight_places( const char *ptr)
{
   return( (double)atoi( ptr) + (double)atoi(ptr + 4) * 1e-8);
}

/* Meteor 2-08                                                           */
/* 1 13113U          88245.60005115 0.00000076           63463-4 0  5998 */
/* 2 13113  82.5386 288.0994 0015973 147.1294 213.0868 13.83869004325321 */

#define J2000 2451545.5
#define J1900 (J2000 - 36525. - 1.)

/* parse_elements returns:
         0 if the elements are parsed without error;
         1 if they're OK except the first line has a checksum error;
         2 if they're OK except the second line has a checksum error;
         3 if they're OK except both lines have checksum errors;
         a negative value if the lines aren't at all parseable */

int DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t *sat)
{
   int rval, checksum_problem = 0;

   if( *line1 != '1' || *line2 != '2')
      rval = -4;
   else
      {
      rval = tle_checksum( line1);
      if( rval > 0)
         {
         checksum_problem = 1;  /* there's a checksum problem,  but it's */
         rval = 0;              /* not fatal; continue processing the TLE */
         }
      }

   if( rval)
      rval -= 100;
   else
      {
      rval = tle_checksum( line2);
      if( rval > 0)
         {
         checksum_problem |= 2;  /* there's a checksum problem,  but it's */
         rval = 0;               /* not fatal; continue processing the TLE */
         }
      }

   if( !rval)
      {
      char tbuff[13];
      int year = line1[19] - '0';

      if( line1[18] >= '0')
         year += (line1[18] - '0') * 10;
      if( year < 57)          /* cycle around Y2K */
         year += 100;
      sat->epoch = get_eight_places( line1 + 20) + J1900
             + (double)( year * 365 + (year - 1) / 4);
      sat->norad_number = get_norad_number( line1 + 2);
      memcpy( tbuff, line1 + 64, 4);
      tbuff[4] = '\0';
      sat->bulletin_number = atoi( tbuff);
      sat->classification = line1[7];       /* almost always 'U' */
      memcpy( sat->intl_desig, line1 + 9, 8);
      if( !memcmp( sat->intl_desig, "     ", 5))
         {  /* usually 'analyst' object w/o international (COSPAR) desig; */
         int i, n = sat->norad_number;      /* set launch 000,  year/part */
                                            /* data mapped from NORAD #   */
         for( i = 7; i > 4; i--, n /= 26)
            sat->intl_desig[i] = 'A' + n % 26;
         sat->intl_desig[2] = sat->intl_desig[3] = sat->intl_desig[4] = '0';
         sat->intl_desig[1] = '0' + n % 10;
         sat->intl_desig[0] = '0' + n / 10;
         }
      sat->intl_desig[8] = '\0';
      memcpy( tbuff, line2 + 63, 5);
      tbuff[5] = '\0';
      sat->revolution_number = atoi( tbuff);
      sat->ephemeris_type = line1[62];
      if( sat->ephemeris_type == 'H')
         {
         size_t i;
         double *state_vect = &sat->xincl;

         for( i = 0; i < 3; i++)
            {
            state_vect[i]     = get_high_value( line1 + 33 + i * 10);
            state_vect[i + 3] = get_high_value( line2 + 33 + i * 10) * 1e-4;
            }
         return( 0);
         }

      sat->xmo = (double)get_angle( line2 + 43) * (PI / 180e+4);
      sat->xnodeo = (double)get_angle( line2 + 17) * (PI / 180e+4);
      sat->omegao = (double)get_angle( line2 + 34) * (PI / 180e+4);
      sat->xincl = (double)get_angle( line2 + 8) * (PI / 180e+4);
      sat->eo = atoi( line2 + 26) * 1.e-7;

      /* Make sure mean motion is null-terminated, since rev. no.
          may immediately follow. */
      memcpy( tbuff, line2 + 51, 12);
      tbuff[12] = '\0';
            /* Input mean motion,  derivative of mean motion and second  */
            /* deriv of mean motion,  are all in revolutions and days.   */
            /* Convert them here to radians and minutes:                 */
      sat->xno = get_eight_places( tbuff) * TWOPI / MINUTES_PER_DAY;
      sat->xndt2o = (double)atoi( line1 + 35)
                        * 1.e-8 * TWOPI / MINUTES_PER_DAY_SQUARED;
      if( line1[33] == '-')
         sat->xndt2o *= -1.;
      sat->xndd6o = sci( line1 + 44) * TWOPI / MINUTES_PER_DAY_CUBED;

      sat->bstar = sci( line1 + 53) * AE;
      }
   return( rval ? rval : checksum_problem);
}


================================================
FILE: get_high.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

/* Code to extract elements for high-flying artsats. Give */
/* it the name of the input file of TLEs and a cutoff of  */
/* the mean motion,  and only TLEs with a lower motion    */
/* will be output.                                        */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include "norad.h"

int main( const int argc, const char **argv)
{
   FILE *ifile = fopen((argc > 1 ? argv[1] : "all_tle.txt"), "rb");
   FILE *ofile;
   char line0[200], line1[200], line2[200];
   const double cutoff = (argc > 2 ? atof( argv[2]) : .6);
   const time_t t0 = time( NULL);

   if( !ifile)
      perror( "Input file not opened");
   ofile = (argc > 3 ? fopen( argv[3], "a") : stdout);
   if( !ofile)
      perror( "Output file not opened");
   if( !ifile || !ofile)
      return( -1);
   *line0 = *line1 = '\0';
   fprintf( ofile, "# Added %.24s UTC\n", asctime( gmtime( &t0)));
   while( fgets( line2, sizeof( line2), ifile))
      {
      if( *line2 == '2' && *line1 == '1'
               && !tle_checksum( line1) && !tle_checksum( line2)
               && atof( line2 + 52) < cutoff)
         fprintf( ofile, "%s%s%s", line0, line1, line2);
      strcpy( line0, line1);
      strcpy( line1, line2);
      }
   fclose( ifile);
   return( 0);
}


================================================
FILE: get_vect.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.

   This code can read a TLE and compute a geocentric state vector,
formatted such that Find_Orb can then read it in.  I did this
partly to test the hypothesis that if you compute a state vector
from Space-Track TLEs at their epoch,  you get the "actual" motion.
That is to say,  you could numerically integrate it to get a better
result.  This turns out not to be the case.  Space-Track TLEs may
be a best-fit to a set of observations or (as with my own TLEs)
a best fit to a numerically integrated ephemeris,  but there
doesn't seem to be a way to improve them by doing a numerical
integration.

   My second purpose was to be able to feed the state vector created
by this program into Find_Orb as an initial orbit guess.  For that
purpose,  it seems to work.  You see large residuals as a result
of the difference between numerical integration and SGP4/SDP4.
But it gets you close enough that you can then do differential
corrections (least-squares fitting).         */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include "norad.h"
#include "watdefs.h"
#include "afuncs.h"
#include "date.h"

#define PI \
   3.1415926535897932384626433832795028841971693993751058209749445923

int main( const int argc, const char **argv)
{
   FILE *ifile;
   const char *filename = "all_tle.txt";
   char line0[100], line1[100], line2[100];
   int i;
   const char *norad = NULL, *intl = NULL;
   double jd = 0.;
   int is_j2000 = 1, is_equatorial = 1;

   for( i = 1; i < argc; i++)
      if( argv[i][0] == '-')
         {
         const char *arg = (i < argc - 1 && !argv[i][2]
                                          ? argv[i + 1] : argv[i] + 2);

         switch( argv[i][1])
            {
            case 'n':
               norad = arg;
               printf( "Looking for NORAD %s\n", norad);
               break;
            case 'i':
               intl = arg;
               printf( "Looking for international ID %s\n", intl);
               break;
            case 't':
               jd = get_time_from_string( 0., arg, FULL_CTIME_YMD, NULL);
               break;
            case 'd':
               is_j2000 = 0;
               break;
            default:
               printf( "'%s': unrecognized option\n", argv[i]);
               return( -1);
               break;
            }
         }
   if( argc > 1 && argv[1][0] != '-')
      filename = argv[1];
   ifile = fopen( filename, "rb");
   if( !ifile)
      {
      fprintf( stderr, "Couldn't open '%s': ", filename);
      perror( "");
      return( -1);
      }
   *line0 = *line1 = '\0';
   while( fgets( line2, sizeof( line2), ifile))
      {
      if( *line1 == '1' && (!norad || !memcmp( line1 + 2, norad, 5))
                        && (!intl || !memcmp( line1 + 9, intl, strlen( intl)))
                   && *line2 == '2')
         {
         tle_t tle;
         const int err_code = parse_elements( line1, line2, &tle);

         if( err_code >= 0)
            {
            const int is_deep = select_ephemeris( &tle);
            double state[6], state_j2000[6], precess_matrix[9];
            double params[N_SAT_PARAMS], t_since;
            const double epoch_tdt =
                        tle.epoch + td_minus_utc( tle.epoch) / seconds_per_day;
            const double J2000 = 2451545.;
            double *state_to_show;

            if( !jd)
               jd = epoch_tdt;
            t_since = (jd - epoch_tdt) * minutes_per_day;
            if( is_deep)
               {
               SDP4_init( params, &tle);
               SDP4( t_since, &tle, params, state, state + 3);
               }
            else
               {
               SGP4_init( params, &tle);
               SGP4( t_since, &tle, params, state, state + 3);
               }
            if( strlen( line0) < 60)
               printf( "%s", line0);
            setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.);
            precess_vector( precess_matrix, state, state_j2000);
            precess_vector( precess_matrix, state + 3, state_j2000 + 3);
            state_to_show = (is_j2000 ? state_j2000 : state);
            printf( " %.6f %.6s\n", jd, line1 + 9);
            printf( " %.5f %.5f %.5f 0408   # Ctr 3 km sec %s %s\n",
                           state_to_show[0], state_to_show[1], state_to_show[2],
                           is_equatorial ? "eq" : "ecl",
                           is_j2000 ? "" : "of_date");
            printf( " %.5f %.5f %.5f 0 0 0\n",
                           state_to_show[3] / seconds_per_minute,
                           state_to_show[4] / seconds_per_minute,
                           state_to_show[5] / seconds_per_minute);
            }
         }
      strcpy( line0, line1);
      strcpy( line1, line2);
      }
   fclose( ifile);
   return( 0);
}


================================================
FILE: line2.cpp
================================================
/* See LICENSE.   NOTE that this has been obsoleted by the 'add_off.c'
program in the 'lunar' repository (q.v.).  The only use this code would
have would be for a spacecraft getting astrometric data for which we
don't have Horizons data.  I don't expect that to happen.  With that
disclaimer :

   The Minor Planet Center accepts astrometry from spacecraft using
a modification of their usual 80-column "punched-card" format.  A
second line is used to tell you where the spacecraft was,  relative
to the geocenter.

https://minorplanetcenter.net/iau/info/SatelliteObs.html

   This code can reset those positions for Earth-orbiting spacecraft using
TLEs ('two-line elements';  https://www.projectpluto.com/tle_info.htm.)
This helps when accurate positions are not provided or completely trusted
or appear to be bad.

   This code will read the 80-column astrometry and,  when it finds a
spacecraft position report (the "second line"),  look through the TLE
file for a matching TLE.  If it finds one,  it'll compute the spacecraft
location for that time and modify the position accordingly.

   This was written specifically to handle a problem with a satellite in
LEO.  TLEs don't exist for heliocentric objects,  but I may revise this
code eventually to add positions for them (this could be done by,  for
example,  requesting them from JPL Horizons).  TLEs exist for TESS,
computed by me,  but some work would be required to make those actually
function properly (the numbers are larger and the format is somewhat
different as a result).  So this really works,  at present,  only for
(C51) WISE, (C52) Swift,  and (C53) NEOSSat.             */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <math.h>
#include "norad.h"
#include "watdefs.h"
#include "afuncs.h"
#include "mpc_func.h"
#include "stringex.h"

#define PI \
   3.1415926535897932384626433832795028841971693993751058209749445923

int find_tle( tle_t *tle, const char *filename, const int norad_no)
{
   FILE *ifile = fopen( filename, "rb");
   char line0[100], line1[100], line2[100];
   int rval = -1;

   if( !ifile)
      {
      fprintf( stderr, "Couldn't open TLE file '%s'\n", filename);
      exit( -1);
      }
   *line0 = *line1 = '\0';
   while( rval && fgets( line2, sizeof( line2), ifile))
      {
      if( *line1 == '1' && *line2 == '2' && atoi( line1 + 2) == norad_no)
         if( parse_elements( line1, line2, tle) >= 0)
            rval = 0;
      strlcpy_error( line0, line1);
      strlcpy_error( line1, line2);
      }
   fclose( ifile);
   if( rval)
      {
      fprintf( stderr, "Couldn't find TLEs for %5d in TLE file '%s'\n",
                           norad_no, filename);
      exit( -1);
      }
   return( rval);
}

/*
C49 = 29510 = 2006-047A = STEREO-A
C50 = 29511 = 2006-047B = STEREO-B
C51 = 36119 = 2009-071A = WISE   *
C52 = 28485 = 2004-047A = Swift   *
C53 = 39089 = 2013-009D = NEOSSat  *
C54 = 28928 = 2006-001A = New Horizons
C55 = 34380 = 2009-011A = Kepler
C56 = 41043 = 2015-070A = LISA-Pathfinder
C57 = 43435 = 2018-038A = TESS      *
   Note that only the asterisked objects are actually in earth orbit
and therefore have TLEs.  But I may try to figure out some way to add
in positions for the heliocentric spacecraft later.  */

static int mpc_code_to_norad_number( const char *mpc_code)
{
   const char *codes = "C49 C50 C51 C52 C53 C54 C55 C56 C57 ";
   const int norad_numbers[] = { 29510, 29511, 36119, 28485, 39089,
                                        28928, 34380, 41043, 43435 };
   int i;

   for( i = 0; codes[i * 4]; i++)
      if( !memcmp( codes + i * 4, mpc_code, 3))
         return( norad_numbers[i]);
   assert( 1);          /* not supposed to ever happen,  unless a new */
   return( 0);          /* satellite has been added */
}

int main( const int argc, const char **argv)
{
   const char *tle_filename = (argc > 2 ? argv[2] : "all_tle.txt");
   const char *astrometry_filename = argv[1];
   FILE *ifile = (argc > 1 ? fopen( astrometry_filename, "rb") : NULL);
   int is_deep = 0, curr_norad = 0;
   char buff[200];
   double params[N_SAT_PARAMS];
   const double J2000 = 2451545.;
   tle_t tle;

   if( argc > 1 && !ifile)
      fprintf( stderr, "'%s' not found : %s\n", astrometry_filename, strerror( errno));
   if( !ifile)
      {
      fprintf( stderr, "'line2' takes as a command-line argument the name of the input\n"
                       "astrometry file.  It sends astrometry with corrected/added\n"
                       "spacecraft locations to stdout.\n");
      exit( -1);
      }
   while( fgets( buff, sizeof( buff), ifile))
      {
      double jd;

      if( strlen( buff) > 80 && buff[14] == 's'
            && (jd = extract_date_from_mpc_report( buff, NULL)) > 2400000.)
         {
         double state[6], state_j2000[6], precess_matrix[9];
         double t_since;
         const int norad_number = mpc_code_to_norad_number( buff + 77);
         int i;

         if( curr_norad != norad_number)
            {
            curr_norad = norad_number;
            find_tle( &tle, tle_filename, norad_number);
            is_deep = select_ephemeris( &tle);
            if( is_deep)
               SDP4_init( params, &tle);
            else
               SGP4_init( params, &tle);
            }
         t_since = (jd - tle.epoch) * minutes_per_day;
         if( is_deep)
            SDP4( t_since, &tle, params, state, state + 3);
         else
            SGP4( t_since, &tle, params, state, state + 3);
         setup_precession( precess_matrix, 2000. + (jd - J2000) / 365.25, 2000.);
         precess_vector( precess_matrix, state, state_j2000);
         precess_vector( precess_matrix, state + 3, state_j2000 + 3);
         for( i = 0; i < 3; i++)
            {
            char *tptr = buff + 34 + i * 12;

            snprintf_err( tptr, 12, "%11.4f", fabs( state_j2000[i]));
            *tptr = (state_j2000[i] > 0. ? '+' : '-');
            tptr[11] = ' ';
            }
         }
      printf( "%s", buff);
      }
   fclose( ifile);
   return( 0);
}


================================================
FILE: makefile
================================================
# GNU MAKE Makefile for artsat code and utilities
#
# Usage: make [W64=Y] [W32=Y] [MSWIN=Y] [tgt]
#
#	where tgt can be any of:
# [all|get_high|mergetle|obs_tes2|...]
#
# ...see below for complete list.  Note that you have to 'make tle_date' and/or
# 'make tle_date.cgi' separately;  they have a dependency on the 'lunar'
# library (also on my GitHub site).
#
#	'W64'/'W32' = cross-compile for 64- or 32-bit Windows,  using MinGW,
#     on a Linux box
#	'MSWIN' = compile for Windows,  using MinGW and PDCurses,  on a Windows machine
#	'CC=clang' or 'CC=g++-4.8' = use clang or older GCC
#    (I've used gmake CLANG=Y on PC-BSD;  probably works on OS/X too)
# None of these: compile using default g++ on Linux,  for Linux
#

# As CC is an implicit variable, a simple CC?=g++ doesn't work.
# We have to use this trick from https://stackoverflow.com/a/42958970
ifeq ($(origin CC),default)
	CC=gcc
	CXX=g++
endif

ifeq ($(shell uname -s),FreeBSD)
	CC=cc
	CXX=c++
endif

EXE=
RM=rm -f

LIB_DIR=$(INSTALL_DIR)/lib

ifdef W64
	CC=x86_64-w64-mingw32-gcc
	CXX=x86_64-w64-mingw32-g++
	EXE=.exe
	LIB_DIR=$(INSTALL_DIR)/win_lib
endif

ifdef W32
	CC=i686-w64-mingw32-gcc
	CXX=i686-w64-mingw32-g++
	EXE=.exe
	LIB_DIR=$(INSTALL_DIR)/win_lib32
endif

# I'm using 'mkdir -p' to avoid error messages if the directory exists.
# It may fail on very old systems,  and will probably fail on non-POSIX
# systems.  If so,  change to '-mkdir' and ignore errors.

ifeq ($(EXE),.exe)
	MKDIR=-mkdir
else
	ZLIB=-lz
	MKDIR=mkdir -p
endif

# You can have your include files in ~/include and libraries in
# ~/lib,  in which case only the current user can use them;  or
# (with root privileges) you can install them to /usr/local/include
# and /usr/local/lib for all to enjoy.

PREFIX?=~
ifdef GLOBAL
	INSTALL_DIR=/usr/local
else
	INSTALL_DIR=$(PREFIX)
endif

INCL=$(INSTALL_DIR)/include

all: dropouts$(EXE) fake_ast$(EXE) fix_tles$(EXE) get_high$(EXE) \
	line2$(EXE) mergetle$(EXE) obs_tes2$(EXE) obs_test$(EXE) \
	out_comp$(EXE) sat_cgi$(EXE) sat_eph$(EXE) sat_id$(EXE) \
	sat_id2$(EXE) sat_id3$(EXE) summarize$(EXE) \
	test_des$(EXE) test_out$(EXE) test_sat$(EXE) test2$(EXE) tle2mpc$(EXE)

CFLAGS+=-Wextra -Wall -O3 -pedantic -Wshadow

ifdef UCHAR
	CFLAGS += -funsigned-char
endif

ifdef DEBUG
	CFLAGS += -g
endif

ifndef NO_ERRORS
	CFLAGS += -Werror
endif


clean:
	$(RM) *.o
	$(RM) dropouts$(EXE)
	$(RM) fake_ast$(EXE)
	$(RM) fix_tles$(EXE)
	$(RM) get_high$(EXE)
	$(RM) get_vect$(EXE)
	$(RM) libsatell.a
	$(RM) line2$(EXE)
	$(RM) mergetle$(EXE)
	$(RM) obs_tes2$(EXE)
	$(RM) obs_test$(EXE)
	$(RM) out_comp$(EXE)
	$(RM) sat_cgi$(EXE)
	$(RM) sat_eph$(EXE)
	$(RM) sat_id$(EXE)
	$(RM) sat_id2$(EXE)
	$(RM) sat_id3$(EXE)
	$(RM) summarize$(EXE)
	$(RM) test2$(EXE)
	$(RM) test_des$(EXE)
	$(RM) test_out$(EXE)
	$(RM) test_sat$(EXE)
	$(RM) tle2mpc$(EXE)
	$(RM) tle_date$(EXE)
	$(RM) tle_date.cgi

install:
	$(MKDIR) $(LIB_DIR)
	cp libsatell.a $(LIB_DIR)
	cp norad.h     $(INSTALL_DIR)/include
	$(MKDIR) $(INSTALL_DIR)/bin
	cp sat_id$(EXE)  $(INSTALL_DIR)/bin

install_lib:
	$(MKDIR) $(LIB_DIR)
	cp libsatell.a $(LIB_DIR)
	cp norad.h     $(INSTALL_DIR)/include

uninstall:
	rm $(INSTALL_DIR)/lib/libsatell.a
	rm $(INSTALL_DIR)/include/norad.h
	rm $(INSTALL_DIR)/bin/sat_id

uninstall_lib:
	rm $(INSTALL_DIR)/lib/libsatell.a
	rm $(INSTALL_DIR)/include/norad.h

OBJS= sgp.o sgp4.o sgp8.o sdp4.o sdp8.o deep.o basics.o get_el.o common.o tle_out.o

get_high$(EXE):	 get_high.o get_el.o
	$(CC) $(CFLAGS) -o get_high$(EXE) get_high.o get_el.o

mergetle$(EXE):	 mergetle.o
	$(CC) $(CFLAGS) -o mergetle$(EXE) mergetle.o -lm

dropouts$(EXE):	 dropouts.o
	$(CC) $(CFLAGS) -o dropouts$(EXE) dropouts.o

obs_tes2$(EXE):	 obs_tes2.o observe.o libsatell.a
	$(CC) $(CFLAGS) -o obs_tes2$(EXE) obs_tes2.o observe.o libsatell.a -lm

obs_test$(EXE):	 obs_test.o observe.o libsatell.a
	$(CC) $(CFLAGS) -o obs_test$(EXE) obs_test.o observe.o libsatell.a -lm

fake_ast$(EXE):	 fake_ast.o observe.o libsatell.a
	$(CC) $(CFLAGS) -o fake_ast$(EXE) fake_ast.o observe.o libsatell.a -lm

fix_tles$(EXE):	 fix_tles.o libsatell.a
	$(CC) $(CFLAGS) -o fix_tles$(EXE) fix_tles.o libsatell.a -lm

get_vect$(EXE):	 	get_vect.cpp	observe.o libsatell.a
	$(CXX) $(CFLAGS) -o get_vect$(EXE) -I $(INCL) get_vect.cpp observe.o libsatell.a -lm -L $(LIB_DIR) -llunar

line2$(EXE):	 	line2.cpp libsatell.a
	$(CXX) $(CFLAGS) -o line2$(EXE) -I $(INCL) line2.cpp libsatell.a -lm -L $(LIB_DIR) -llunar

out_comp$(EXE):	 out_comp.o
	$(CC) $(CFLAGS) -o out_comp$(EXE) out_comp.o -lm

libsatell.a: $(OBJS)
	rm -f libsatell.a
	ar rv libsatell.a $(OBJS)

sat_eph$(EXE):	 	sat_eph.c	observe.o libsatell.a
	$(CC) $(CFLAGS) -o sat_eph$(EXE) -I $(INCL) sat_eph.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)

sat_cgi$(EXE):	 	sat_eph.c	observe.o libsatell.a
	$(CC) $(CFLAGS) -o sat_cgi$(EXE) -I $(INCL) sat_eph.c observe.o -DON_LINE_VERSION libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)

sat_id$(EXE):	 	sat_id.cpp sat_util.o	observe.o libsatell.a
	$(CXX) $(CFLAGS) -o sat_id$(EXE) -I $(INCL) sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)

sat_id2$(EXE):	 	sat_id2.cpp sat_id.cpp sat_util.o observe.o libsatell.a
	$(CXX) $(CFLAGS) -o sat_id2$(EXE) -I $(INCL) -DON_LINE_VERSION sat_id2.cpp sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)

sat_id3$(EXE):	 	sat_id3.cpp sat_id.cpp sat_util.o observe.o libsatell.a
	$(CXX) $(CFLAGS) -o sat_id3$(EXE) -I $(INCL) -DON_LINE_VERSION sat_id3.cpp sat_id.cpp sat_util.o observe.o libsatell.a -lm -L $(LIB_DIR) -llunar $(ZLIB)

summarize$(EXE):	 	summarize.c	observe.o libsatell.a
	$(CC) $(CFLAGS) -o summarize$(EXE) -I $(INCL) summarize.c observe.o libsatell.a -lm -L $(LIB_DIR) -llunar

test2$(EXE):	 	test2.o sgp.o libsatell.a
	$(CC) $(CFLAGS) -o test2$(EXE) test2.o sgp.o libsatell.a -lm

tle_date$(EXE):	 	tle_date.o
	$(CC) $(CFLAGS) -o tle_date$(EXE) tle_date.o -L $(LIB_DIR) -llunar -lm

tle_date.o: tle_date.c
	$(CC) $(CFLAGS) -o tle_date.o -c -I../include tle_date.c

tle_date.cgi:	 	tle_date.c
	$(CC) $(CFLAGS) -o tle_date.cgi  -I../include -DON_LINE_VERSION tle_date.c -L $(LIB_DIR) -llunar

tle2mpc$(EXE):	 	tle2mpc.cpp libsatell.a
	$(CXX) $(CFLAGS) -o tle2mpc$(EXE) -I $(INCL) tle2mpc.cpp libsatell.a -lm -L $(LIB_DIR) -llunar

test_des$(EXE):	 test_des.o libsatell.a
	$(CC) $(CFLAGS) -o test_des$(EXE) test_des.o libsatell.a -lm

test_out$(EXE):	 test_out.o tle_out.o get_el.o sgp4.o common.o
	$(CC) $(CFLAGS) -o test_out$(EXE) test_out.o tle_out.o get_el.o sgp4.o common.o -lm

test_sat$(EXE):	 test_sat.o libsatell.a
	$(CC) $(CFLAGS) -o test_sat$(EXE) test_sat.o libsatell.a -lm

.cpp.o:
	$(CXX) $(CFLAGS) -c $<

.c.o:
	$(CC) $(CFLAGS) -c $<


================================================
FILE: mergetle.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.

Code to read one or more files of TLEs,  remove duplicates,
and sort them according to various possible criteria (eccentricity,
orbital period,  etc.)   */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define TLE struct tle
#define MAX_TLES 200000

/* As of late 2020,  NORAD numbers are stored in five digits.  There's
some possibility of that being bumped up to nine digits,  in which
case I'll have to revisit this code.         */

#define MAX_NORAD_NUMBER 100000

TLE
   {
   char name_line[80], line1[80], line2[80];
   };

int n_duplicates = 0, heavens_above_html_tles = 0;

int load_tles_from_file( FILE *ifile, TLE *tles, char *already_found)
{
   int rval = 0;
   char buff[80], prev_line[80];

   *prev_line = '\0';
   while( fgets( buff, sizeof( buff), ifile))
      {
      if( *buff == '1' && heavens_above_html_tles &&
                            !memcmp( buff + 69, "<B></B>", 7))
         strcpy( buff + 69, "\n");
      if( *buff == '1' && strlen( buff) > 69 && buff[69] < ' ')
         {
         char buff2[80];
         const int norad_number = atoi( buff + 2);

         if( fgets( buff2, sizeof( buff2), ifile))
            if( *buff2 == '2' && strlen( buff2) > 69 && buff2[69] < ' ')
               {
               if( !already_found[norad_number])
                  {
                  if( heavens_above_html_tles)     /* can't use HA names */
                     *prev_line = '\0';
                  strcpy( tles[rval].name_line, prev_line);
                  strcpy( tles[rval].line1, buff);
                  strcpy( tles[rval].line2, buff2);
                  already_found[norad_number] = 1;
                  rval++;
                  *buff = '\0';
                  }
               else
                  n_duplicates++;
               }
         }
      strcpy( prev_line, buff);
      }
   return( rval);
}

FILE *test_fopen( const char *filename, const char *permits)
{
   FILE *rval = fopen( filename, permits);

   if( !rval)
      {
      printf( "%s not opened\n", filename);
      exit( -1);
      }
   return( rval);
}

void show_tle( FILE *ofile, const TLE *tle)
{
   fprintf( ofile, "%s", tle->name_line);
   fprintf( ofile, "%s", tle->line1);
   fprintf( ofile, "%s", tle->line2);
}

static double get_perigee( const TLE *tle)
{
   const double ecc = atof( tle->line2 + 26) * 1e-7;
   const double revs_per_day = atof( tle->line2 + 52);
   const double semimajor = pow( revs_per_day, -2. / 3.);

   return( semimajor * (1. - ecc));
}

static int compare_doubles( const char *buff1, const char *buff2)
{
   const double d1 = atof( buff1);
   const double d2 = atof( buff2);
   int rval;

   if( d1 < d2)
      rval = -1;
   else if( d1 > d2)
      rval = 1;
   else
      rval = 0;
   return( rval);
}

int tle_compare( const TLE *tle1, const TLE *tle2, const char sort_method)
{
   int i, rval = 0;

   switch( sort_method)
      {
      case 'n': case 'N':        /* sort by NORAD number */
         rval = atoi( tle1->line1 + 2) - atoi( tle2->line1 + 2);
         break;
      case 'c': case 'C':        /* sort by COSPAR (international) desig */
         if( tle1->line1[9] >= '5' && tle2->line1[9] < '5')
            rval = -1;
         else if( tle2->line1[9] >= '5' && tle1->line1[9] < '5')
            rval = 1;
         else        /* COSPAR IDs are from the same century */
            rval = memcmp( tle1->line1 + 9, tle2->line1 + 9, 8);
         break;
      case 'm': case 'M':        /* sort by mean motion */
         rval = compare_doubles( tle1->line2 + 52, tle2->line2 + 52);
         break;
      case 'e': case 'E':        /* sort by eccentricity */
         for( i = 26; !rval && i < 33; i++)
            rval = tle1->line2[i] - tle2->line2[i];
         break;
      case 'p': case 'P':        /* sort by epoch */
         for( i = 18; !rval && i < 32; i++)
            rval = tle1->line1[i] - tle2->line1[i];
         break;
      case 'i': case 'I':        /* sort by incl */
         rval = compare_doubles( tle1->line2 + 8, tle2->line2 + 8);
         break;
      case 'o': case 'O':        /* sort by ascending node */
         rval = compare_doubles( tle1->line2 + 17, tle2->line2 + 17);
         break;
      case 'q':
         {
         const double q1 = get_perigee( tle1);
         const double q2 = get_perigee( tle2);

         rval = ( q1 > q2 ? 1 : -1);
         }
         break;
      }
   if( sort_method >= 'A' && sort_method <= 'Z')
      rval = -rval;
   return( rval);
}

      /* MS Windows lacks the re-entrant qsort_r;  we have to use  */
      /* plain old non-re-entrant qsort and a global variable.     */
      /* BSD has it,  but with the wrong order of arguments.       */
#ifdef __linux
#define HAVE_REENTRANT_QSORT
#endif

#ifdef HAVE_REENTRANT_QSORT
int tle_compare_for_qsort_r( const void *a, const void *b, void *c)
{
   return( tle_compare( (const TLE *)a, (const TLE *)b, *(char *)c));
}
#else
static char comparison_method;

int tle_compare_for_qsort( const void *a, const void *b)
{
   return( tle_compare( (const TLE *)a, (const TLE *)b, comparison_method));
}
#endif


static void error_exit( void)
{
   printf( "'mergetle' will merge TLEs from one or more files.  Optionally,\n");
   printf( "the output can be sorted.  For example:\n\n");
   printf( "mergetle geosynch.tle molniya.tle visual.tle -se -oall.tle\n\n");
   printf( "would create a file 'all.tle',  containing elements from the three\n");
   printf( "input .tle files,  sorted by NORAD number.  If a satellite appears\n");
   printf( "in more than one .tle,  the .tle from the first file on the command\n");
   printf( "line is used.  Options are:\n\n");
   printf( "-sn, -sN       Sort output by ascending/descending NORAD number\n");
   printf( "-sc, -sC       Sort output by ascending/descending COSPAR desig\n");
   printf( "-sm, -sM       Sort output by ascending/descending mean motion\n");
   printf( "-se, -sE       Sort output by ascending/descending eccentricity\n");
   printf( "-sp, -sP       Sort output by ascending/descending epoch\n");
   printf( "-si, -sI       Sort output by ascending/descending inclination\n");
   printf( "-so, -sO       Sort output by ascending/descending ascending node\n");
   printf( "-sq            Sort output by ascending perigee\n");
   printf( "-o(filename)   Set name of output .tle file (default is out.tle)\n");
   printf( "-n             Remove names from input TLEs\n");
   printf( "-h             Remove HTML tags from input.  This allows you to extract\n");
   printf( "                 TLEs from certain Web pages.\n");
   exit( -1);
}

int main( const int argc, const char **argv)
{
   TLE *tles = (TLE *)calloc( MAX_TLES, sizeof( TLE));
   char *already_found = (char *)calloc( MAX_NORAD_NUMBER, sizeof( char));
   int n_found = 0, i, strip_names = 0;
   char sort_method = 0;
   const char *output_filename = "out.tle";
   FILE *ofile;

   if( argc < 2)
      error_exit( );
   for( i = 1; i < argc; i++)
      if( argv[i][0] == '-')
         switch( argv[i][1])
            {
            case 'o':
               output_filename = argv[i] + 2;
               break;
            case 's':
               sort_method = argv[i][2];
               break;
            case 'n':
               strip_names = 1;
               printf( "Names will be removed in output\n");
               break;
            case 'h':
               heavens_above_html_tles = 1;
               printf( "HTML tags will be removed from input\n");
               break;
            default:
               printf( "Ignoring unknown option '%s'\n", argv[i]);
               break;
            }
      else
         {
         FILE *ifile = test_fopen( argv[i], "rb");
         int n;

         n_duplicates = 0;
         n = load_tles_from_file( ifile, tles + n_found, already_found);
         printf( "%d TLEs added from %s,  with %d duplicates found\n",
                            n, argv[i], n_duplicates);
         n_found += n;
         fclose( ifile);
         }
   free( already_found);
   if( strip_names)
      for( i = 0; i < n_found; i++)
         tles[i].name_line[0] = '\0';
#ifndef HAVE_REENTRANT_QSORT
   comparison_method = sort_method;
   qsort( tles, n_found, sizeof( TLE), tle_compare_for_qsort);
#else
   qsort_r( tles, n_found, sizeof( TLE), tle_compare_for_qsort_r, &sort_method);
#endif
   ofile = test_fopen( output_filename, "wb");
   for( i = 0; i < n_found; i++)
      show_tle( ofile, tles + i);
}


================================================
FILE: msvc.mak
================================================
# Makefile for MSVC
all:  dropouts.exe fix_tles.exe line2.exe mergetle.exe obs_test.exe \
   obs_tes2.exe out_comp.exe sat_eph.exe sat_id.exe  \
   test2.exe test_out.exe test_sat.exe tle2mpc.exe

COMMON_FLAGS=-nologo -W3 -EHsc -c -FD -D_CRT_SECURE_NO_WARNINGS
RM=del

!ifdef BITS_32
BITS=32
!else
BITS=64
!endif

CFLAGS=-MT -O1 -D "NDEBUG" $(COMMON_FLAGS)
LINK=link /nologo /stack:0x8800

OBJS= sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \
     basics.obj get_el.obj common.obj tle_out.obj

dropouts.exe: dropouts.obj
   $(LINK) dropouts.obj

fix_tles.exe: fix_tles.obj sat_code$(BITS).lib
   $(LINK)    fix_tles.obj sat_code$(BITS).lib

line2.exe: line2.obj observe.obj sat_code$(BITS).lib
   $(LINK) line2.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib

mergetle.exe: mergetle.obj
   $(LINK) mergetle.obj

obs_test.exe: obs_test.obj observe.obj sat_code$(BITS).lib
   $(LINK)    obs_test.obj observe.obj sat_code$(BITS).lib

obs_tes2.exe: obs_tes2.obj observe.obj sat_code$(BITS).lib
   $(LINK)    obs_tes2.obj observe.obj sat_code$(BITS).lib

out_comp.exe: out_comp.obj
   $(LINK)    out_comp.obj

sat_code$(BITS).lib: $(OBJS)
   del sat_code$(BITS).lib
   lib /OUT:sat_code$(BITS).lib $(OBJS)

sat_id.exe: sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib
   $(LINK)  sat_id.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib

sat_eph.exe: sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib
    $(LINK)  sat_eph.obj sat_util.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib

test2.exe: test2.obj sat_code$(BITS).lib
   $(LINK) test2.obj sat_code$(BITS).lib

test_out.exe: test_out.obj sat_code$(BITS).lib
   $(LINK)    test_out.obj sat_code$(BITS).lib

test_sat.exe: test_sat.obj sat_code$(BITS).lib
   $(LINK)    test_sat.obj sat_code$(BITS).lib

tle2mpc.exe: tle2mpc.obj observe.obj sat_code$(BITS).lib
   $(LINK)   tle2mpc.obj observe.obj sat_code$(BITS).lib lunar$(BITS).lib

.cpp.obj:
   cl $(CFLAGS) $<

clean:
   del *.obj
   del *.exe
   del *.idb
   del sat_code$(BITS).lib

install:
   copy norad.h ..\myincl
   copy sat_code$(BITS).lib ..\lib


================================================
FILE: msvc_dll.mak
================================================
# MSVC makefile for a DLL version
# NOTE: hasn't been used or updated in years;  some work required
# before it would be fit for purpose.

all:  test2.exe test_sat.exe obs_test.exe obs_tes2.exe sat_id.exe test_out.exe sm_sat.dll out_comp.exe

out_comp.exe: out_comp.cpp
   cl -W3 -Ox -nologo out_comp.cpp

test2.exe: test2.obj sat_code.lib
   link test2.obj sat_code.lib

test_sat.exe: test_sat.obj sat_code.lib
   cl -nologo test_sat.obj sat_code.lib

test_out.exe: test_out.obj tle_out.obj sat_code.lib
   cl -nologo test_out.obj tle_out.obj sat_code.lib

obs_test.exe: obs_test.obj observe.obj sat_code.lib
   cl -nologo obs_test.obj observe.obj sat_code.lib

obs_tes2.exe: obs_tes2.obj observe.obj sat_code.lib
   cl -nologo obs_tes2.obj observe.obj sat_code.lib

sat_id.exe:   sat_id.obj sat_util.obj sat_code.lib
   cl -nologo sat_id.obj sat_util.obj sat_code.lib

sat_code.lib: sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \
   basics.obj get_el.obj observe.obj common.obj
   del sat_code.lib
   del sat_code.dll
   link /DLL /IMPLIB:sat_code.lib /DEF:sat_code.def /MAP:sat_code.map \
            sgp.obj sgp4.obj sgp8.obj sdp4.obj sdp8.obj deep.obj \
            basics.obj get_el.obj observe.obj common.obj

sm_sat.dll: sgp4.obj basics.obj get_el.obj common.obj
   del sm_sat.lib
   del sm_sat.dll
   link /DLL /IMPLIB:sm_sat.lib /DEF:sm_sat.def /MAP:sm_sat.map \
            sgp4.obj basics.obj get_el.obj common.obj

#CFLAGS=-W3 -c -LD -Ox -DRETAIN_PERTURBATION_VALUES_AT_EPOCH
CFLAGS=-W3 -c -LD -Ox -nologo -D_CRT_SECURE_NO_WARNINGS

.cpp.obj:
   cl $(CFLAGS) $<

common.obj:

sgp.obj:

sgp4.obj:

sgp8.obj:

sdp4.obj:

sdp8.obj:

deep.obj:

basics.obj:

get_el.obj:

observe.obj:

test2.obj:

test_out.obj:

test_sat.obj:

tle_out.obj:

obs_test.obj:

obs_tes2.obj:

sat_id.obj:

clean:
   del sat_code.dll
   del *.obj
   del *.exe
   del *.idb
   del sat_code.exp
   del sat_code.map
   del sat_code$(BITS).lib


================================================
FILE: norad.h
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

/*
 *  norad.h v. 01.beta 03/17/2001
 *
 *  Header file for norad.c
 */

#ifndef NORAD_H
#define NORAD_H 1

/* #define RETAIN_PERTURBATION_VALUES_AT_EPOCH 1 */

/* Two-line-element satellite orbital data */
typedef struct
{
  double epoch, xndt2o, xndd6o, bstar;
  double xincl, xnodeo, eo, omegao, xmo, xno;
  int norad_number, bulletin_number, revolution_number;
  char classification;    /* "U" = unclassified;  only type I've seen */
  char ephemeris_type;
  char intl_desig[9];
} tle_t;

   /* NOTE: xndt2o and xndt6o are used only in the "classic" SGP, */
   /* not in SxP4 or SxP8. */
   /* epoch is a Julian Day,  UTC */
   /* xmo = mean anomaly at epoch,  radians */
   /* xno = mean motion at epoch,  radians/minute*/

#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH
   #define DEEP_ARG_T_PARAMS     87
#else
   #define DEEP_ARG_T_PARAMS     81
#endif

#define N_SGP_PARAMS          11
#define N_SGP4_PARAMS         30
#define N_SGP8_PARAMS         25
#define N_SDP4_PARAMS        (10 + DEEP_ARG_T_PARAMS)
#define N_SDP8_PARAMS        (11 + DEEP_ARG_T_PARAMS)

/* 94 = maximum possible size of the 'deep_arg_t' structure,  in 8-byte units */
/* You can use the above constants to minimize the amount of memory used,
   but if you use the following constant,  you can be assured of having
   enough memory for any of the five models: */

#define N_SAT_PARAMS         (11 + DEEP_ARG_T_PARAMS)

/* Byte 63 of the first line of a TLE contains the ephemeris type.  The */
/* following five values are recommended,  but it seems the non-zero    */
/* values are only used internally;  "published" TLEs all have type 0.  */
/* However,  I've had occasion to produce SGP4 TLEs for high-fliers, in */
/* cases where I couldn't get an SDP4 fit.                              */

#define TLE_EPHEMERIS_TYPE_DEFAULT           0
#define TLE_EPHEMERIS_TYPE_SGP               1
#define TLE_EPHEMERIS_TYPE_SGP4              2
#define TLE_EPHEMERIS_TYPE_SDP4              3
#define TLE_EPHEMERIS_TYPE_SGP8              4
#define TLE_EPHEMERIS_TYPE_SDP8              5

#define SXPX_DPSEC_INTEGRATION_ORDER         0
#define SXPX_DUNDEE_COMPLIANCE               1
#define SXPX_ZERO_PERTURBATIONS_AT_EPOCH     2


/* SDP4 and SGP4 can return zero,  or any of the following error/warning codes.
The 'warnings' result in a mathematically reasonable value being returned,
and perigee within the earth is completely reasonable for an object that's
just left the earth or is about to hit it.  The 'errors' mean that no
reasonable position/velocity was determined.       */

#define SXPX_ERR_NEARLY_PARABOLIC         -1
#define SXPX_ERR_NEGATIVE_MAJOR_AXIS      -2
#define SXPX_WARN_ORBIT_WITHIN_EARTH      -3
#define SXPX_WARN_PERIGEE_WITHIN_EARTH    -4
#define SXPX_ERR_NEGATIVE_XN              -5
#define SXPX_ERR_CONVERGENCE_FAIL         -6

/* Function prototypes */
/* norad.c */

         /* The Win32 version can be compiled to make a .DLL,  if the     */
         /* functions are declared to be of type __stdcall... _and_ the   */
         /* functions must be declared to be extern "C",  something I     */
         /* overlooked and added 24 Sep 2002.  The DLL_FUNC macro lets    */
         /* this coexist peacefully with other OSes.                      */

#ifdef _WIN32
#define DLL_FUNC __stdcall
#else
#define DLL_FUNC
#endif

#ifdef __cplusplus
extern "C" {
#endif


void DLL_FUNC SGP_init( double *params, const tle_t *tle);
int  DLL_FUNC SGP(  const double tsince, const tle_t *tle, const double *params,
                                     double *pos, double *vel);

void DLL_FUNC SGP4_init( double *params, const tle_t *tle);
int  DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *params,
                                     double *pos, double *vel);

void DLL_FUNC SGP8_init( double *params, const tle_t *tle);
int  DLL_FUNC SGP8( const double tsince, const tle_t *tle, const double *params,
                                     double *pos, double *vel);

void DLL_FUNC SDP4_init( double *params, const tle_t *tle);
int  DLL_FUNC SDP4( const double tsince, const tle_t *tle, const double *params,
                                     double *pos, double *vel);

void DLL_FUNC SDP8_init( double *params, const tle_t *tle);
int  DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *params,
                                     double *pos, double *vel);

int DLL_FUNC select_ephemeris( const tle_t *tle);
int DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t *sat);
int DLL_FUNC tle_checksum( const char *buff);
void DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle);

void DLL_FUNC sxpx_set_implementation_param( const int param_index,
                                              const int new_param);
void DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size);
void DLL_FUNC lunar_solar_position( const double jd,
                    double *lunar_xyzr, double *solar_xyzr);

#ifdef __cplusplus
}                       /* end of 'extern "C"' section */
#endif

         /* Following are in 'dynamic.cpp',  for C/C++ programs that want  */
         /* to load 'sat_code.dll' and use its functions at runtime.  They */
         /* only make sense in the Win32 world: */
#ifdef _WIN32
int SXPX_init( double *params, const tle_t *tle, const int sxpx_num);
int SXPX( const double tsince, const tle_t *tle, const double *params,
                               double *pos, double *vel, const int sxpx_num);
#endif
#endif


================================================
FILE: norad_in.h
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

#ifndef NORAD_IN_H
#define NORAD_IN_H

/* Common "internal" arguments between deep-space functions;  users of  */
/* the satellite routines shouldn't need to bother with any of this     */

typedef struct
{
  double
  /* Common between SGP4 and SDP4: */
  aodp, cosio, sinio, omgdot, xmdot, xnodot, xnodp,
  /* Used by dpinit part of Deep() */
  eosq, betao, cosio2, sing, cosg, betao2,

  /* Used by dpsec and dpper parts of Deep() */
  xll, omgadf, xnode, em, xinc, xn, t,

       /* 'd####' secular coeffs for 12-hour, e>.5 orbits: */
   d2201, d2211, d3210, d3222, d4410, d4422, d5220, d5232, d5421, d5433,
      /* formerly static to Deep( ),   but more logically part of this struct: */
   atime, del1, del2, del3, e3, ee2, omegaq, pe, pgh, ph, pinc, pl, preep,
   savtsn, se2, se3, sgh2, sgh3, sgh4, sh2, sh3, si2, si3, sl2, sl3,
   sl4, sse, ssg, ssh, ssi, ssl, thgr, xfact, xgh2, xgh3, xgh4, xh2,
   xh3, xi2, xi3, xl2, xl3, xl4, xlamo, xli, xni, xnq,
   zmol, zmos;

         /* Epoch offsets,  described by Rob Matson,  added by BJG, */
         /* then commented out;  I don't think they really ought to */
         /* be used... */
#ifdef RETAIN_PERTURBATION_VALUES_AT_EPOCH
    double pe0, pinc0, pl0, pgh0, ph0;
    int solar_lunar_init_flag;
#endif
    int resonance_flag, synchronous_flag;
} deep_arg_t;

double FMod2p( const double x);
void Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg);
void Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg);
void Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg);

int sxpx_posn_vel( const double xnode, const double a, const double e,
      const double cosio, const double sinio,
      const double xincl, const double omega,
      const double xl, double *pos, double *vel);

typedef struct
{
   double coef, coef1, tsi, s4, unused_a3ovk2, eta;
} init_t;

void sxpx_common_init( double *params, const tle_t *tle,
                                  init_t *init, deep_arg_t *deep_arg);

/* Table of constant values */
#define pi               3.141592653589793238462643383279502884197
#define twopi            (pi*2.)
#define e6a              1.0E-6
#define two_thirds       (2. / 3.)
#define xj3             -2.53881E-6
#define minus_xj3        2.53881E-6
#define earth_radius_in_km           6378.135
#ifndef minutes_per_day
   #define minutes_per_day  1440.
#endif
#define ae                  1.0
#define xj2                 1.082616e-3
#define ck2                 (.5 * xj2 * ae * ae)

      /* xke^2 = earth GM,  in (earth radii)^3/minutes^2.  */
#ifdef OLD_CONSTANTS
#define ck4      6.209887E-7
#define s        1.012229
#define qoms2t   1.880279E-09
#define xke      7.43669161E-2
#else
#define xj4      (-1.65597e-6)
#define ck4      (-.375 * xj4 * ae * ae * ae * ae)
#define s_const  (ae * (1. + 78. / earth_radius_in_km))
#define qoms2t   1.880279159015270643865e-9
#define xke      0.0743669161331734132
#endif

#define a3ovk2   (minus_xj3/ck2*ae*ae*ae)

#endif         /* #ifndef NORAD_IN_H */


================================================
FILE: nu2vect.c
================================================
/* Code to read in the Spektr-RG state vectors from .nu files at

ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/

   and output into a form ingestible by Find_Orb.  (Note that these
haven't been updated for a while;  we've been tracking Spektr-RG
solely through observed astrometry.)  Input lines give

0.000    (always zero,  means J2000)
1        (loop number;  ignore)
2.021030100000E+7    ( = 20210301 = 2021 Mar 01)
 1.649335000000E+05  ( = 16:49:33.5 Moscow time (three hours ahead of UTC))
-1.190093387974E+03  (x-coord, geocentric, equatorial J2000,  in thousands of km)
 9.733125144680E+02  (y-coord,  same system)
 4.956578219661E+02  (z-coord,  same system)
-7.639675611173E-02  (x-velocity, in km/s)
-6.678070845750E-02  (y-velocity)
-2.372125806637E-01  (z-velocity)
 0.000000000000E+00  (ballistic coefficient)
 1.246012245177E-05  (solar radiation coefficient, unitless)

   Note that the file name gives the UTC,  and the time given within
the file is three hours ahead of that.  The positions match those
determined by optical astrometry to within a few kilometers.

   The above solar radiation coefficient means that the sun's gravity
is counteracted by a force 1.24601e-5 times as great.  It corresponds
approximately to an area/mass ratio of 0.015 m^2/kg,  which is
quite close to what we've been getting from the optical astrometry
orbit solution.

   Compile with

gcc -Wextra -Wall -O3 -pedantic nu2vect.c -I../include -L../lib -o nu2vect -llunar

   Python code to convert .nu files to STK ephemeris files is at

https://github.com/Satsir/STK/blob/main/nuToEph.py     */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "stringex.h"

int main( const int argc, const char **argv)
{
   FILE *ifile = (argc == 2 ? fopen( argv[1], "rb") : NULL);
   char buff[90];
   char command[200];
   int i;

   if( !ifile)
      fprintf( stderr, "See comments at start of 'nu2vect.c' for usage\n");
   assert( ifile);
   for( i = 0; i < 10; i++)
      if( fgets( buff, sizeof( buff), ifile))
         {
         const double ival = atof( buff);

         switch( i)
            {
            case 2:
               {
               const int day = (int)ival;

               snprintf_err( buff, sizeof( buff), "%04d-%02d-%02d",
                        day / 10000, (day / 100) % 100, day % 100);
               printf( "Date : %s\n", buff);
               snprintf_err( command, sizeof( command),
                       "find_orb \"-oSpektr-RG = 2019-040A = NORAD 44432\" -v%sT", buff);
               }
               break;
            case 3:
               {
               const int millisec = (int)( ival * 1000.);

               snprintf_err( buff, sizeof( buff), "%02d:%02d:%02d.%03d",
                        millisec / 10000000,
                        (millisec / 100000) % 100,
                        (millisec / 1000) % 100, millisec % 100);
               printf( "Time : %s\n", buff);
               strlcat_error( command, buff);
               strlcat_error( command, "-3h");   /* correction for Moscow time */
               }
               break;
            case 4: case 5: case 6:
               printf( "  Posn %f km\n", ival * 1000.);
               snprintf_append( command, sizeof( command), ",%.2f", ival * 1000.);
               break;
            case 7: case 8: case 9:
               printf( "  Vel  %f km/s\n", ival);
               snprintf_append( command, sizeof( command), ",%.7f", ival);
               break;
            break;
            }
         }
   fclose( ifile);
   printf( "%s,eq,geo,km,s\n", command);
   return( 0);
}


================================================
FILE: nu_readme.txt
================================================
(Translation/additions to ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/00_read.me .
This also applies to the files for Spektr-R in the
ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-r/nu/ directory.)

Format of .nu files :


Line 1:  0.000000000000E+00   // 0 = coordinates are in J2000, geocentric, equatorial
Line 2:  1.000000000000E+00   // orbit number (always zero for Spektr-RG)
Line 3:  2.011111500000E+07   // Epoch date,  YYYYMMDD (example is 2011 11 15)
Line 4:  1.492674329226E+04   // Epoch time, HH:MM:SS (example is 01:49:26.74329226)
Line 5:  5.174588077311E+00   // x-coordinate,  in thousands of kilometers
Line 6: -2.277098498781E+00   // y-coordinate
Line 7: -3.461034587339E+00   // z-coordinate
Line 8:  4.762109598043E+00   // x-velocity,  in kilometers/second
Line 9:  4.109503042673E+00   // y-velocity
Line 10: 4.560902129453E+00   // z-velocity
Line 11: 3.000000000000E-02   // Ballistic coefficient,  m^3/seconds^2/kilograms (?)
Line 12: 1.248000000000E-05   // Solar radiation coefficient,  unitless

   For Spektr-R,  the orbit number is non-zero and gives the number
of completed orbits.  It is not defined for Spektr-RG.

   Note that the file name gives the epoch in UTC.  The epoch given
within the file is three hours ahead of that (Moscow time).  The
positions match those determined by optical astrometry to within about
20 kilometers.  It is possible that the epoch is in TT (plus three
hours);  the difference is below what I can detect.

   The above solar radiation coefficient means that the sun's gravity
is counteracted by a force 1.248e-5 times as great.  It corresponds
approximately to an area/mass ratio of 0.015 m^2/kg,  which is
quite close to what we've been getting from the optical astrometry
orbit solution.


================================================
FILE: obs_tes2.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

/*
    obs_tes2.cpp     12 December 2002

   (Revised slightly December 2012 to fix compiler warning errors.)

   An example 'main' function illustrating how to find which satellite(s)
are within a given radius of a given RA/dec,  as seen from a given
point.  The code reads in a TLE file (name provided as the first
command-line argument).  Details of the observer position,  search
radius,  date/time,  and RA/dec are provided on the command line.
For example:

obs_tes2 alldat.tle -l44.01,-69.9,10 -p90,30 -j2452623.5 -r10

   would hunt through the TLE element file 'alldat.tle' for satellites
visible from latitude +44.01,  longitude -69.9,  altitude 10 metres;
at RA=90 degrees (6h),  dec=+30;  on JD 2452623.5 (UTC);  within a
ten-degree search radius.  (All of these are the default values.)
The output looks like this:

NORAD  Int'l     RA (J2000) dec    Delta Radius  PA Speed
08593U 74089DG  88.7235  22.9622   2293.0  7.15 225 10.75
15830U 85049D   82.5051  34.9711  32143.6  8.99  34  0.24
17642U 81053LQ  88.1471  32.6585   1428.5  3.24 213 17.60
21833U 91088A   80.6400  27.9649  36216.6  9.58  87  0.17

   ...with 'delta'=distance to satellite in km,  'radius'=angular
distance in degrees from the search point,  'PA' = position angle
of motion, 'Speed' = apparent angular rate of motion in
arcminutes/second (or degrees/minute). */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "norad.h"
#include "observe.h"

#define PI 3.141592653589793238462643383279
#define TIME_EPSILON (1./86400.)

int main( const int argc, const char **argv)
{
   const char *tle_file_name = ((argc == 1) ? "alldat.tle" : argv[1]);
   FILE *ifile = fopen( tle_file_name, "rb");
   char line1[100], line2[100];
   double lat = 44.01, lon = -69.9, ht_in_meters = 10.;
   double jd = 2452623.5;   /* 15 Dec 2002 0h UT */
   double search_radius = 10.;     /* default to ten-degree search */
   double target_ra = 90., target_dec = 30.;  /* default search is at RA=6h, dec=+30 */
   double rho_sin_phi, rho_cos_phi, observer_loc[3], observer_loc2[3];
   int i, header_line_shown = 0;

   if( !ifile)
      {
      printf( "Couldn't open input file %s\n", tle_file_name);
      exit( -1);
      }

   for( i = 1; i < argc; i++)
      if( argv[i][0] == '-')
         switch( argv[i][1])
            {
            case 'l':
               sscanf( argv[i] + 2, "%lf,%lf,%lf", &lat, &lon, &ht_in_meters);
               break;
            case 'p':
               sscanf( argv[i] + 2, "%lf,%lf", &target_ra, &target_dec);
               break;
            case 'j':
               jd = atof( argv[i] + 2);
               break;
            case 'r':
               search_radius = atof( argv[i] + 2);
               break;
            default:
               printf( "Unrecognized command-line option '%s'\n", argv[i]);
               exit( -2);
               break;
            }

            /* Figure out where the observer _really_ is,  in Cartesian */
            /* coordinates of date: */
   earth_lat_alt_to_parallax( lat * PI / 180., ht_in_meters, &rho_cos_phi,
                                                             &rho_sin_phi);
   observer_cartesian_coords( jd,
                lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc);
   observer_cartesian_coords( jd + TIME_EPSILON,
                lon * PI / 180., rho_cos_phi, rho_sin_phi, observer_loc2);
   target_ra *= PI / 180.;
   target_dec *= PI / 180.;

   if( fgets( line1, sizeof( line1), ifile))
      while( fgets( line2, sizeof( line2), ifile))
         {
         tle_t tle;     /* Structure for two-line elements set for satellite */

         if( !parse_elements( line1, line2, &tle))    /* hey! we got a TLE! */
            {
            int is_deep = select_ephemeris( &tle);
            double sat_params[N_SAT_PARAMS], radius, d_ra, d_dec;
            double ra, dec, dist_to_satellite, t_since;
            double pos[3]; /* Satellite position vector */
            double unused_delta2;

            t_since = (jd - tle.epoch) * 1440.;
            if( is_deep)
               {
               SDP4_init( sat_params, &tle);
               SDP4( t_since, &tle, sat_params, pos, NULL);
               }
            else
               {
               SGP4_init( sat_params, &tle);
               SGP4( t_since, &tle, sat_params, pos, NULL);
               }
            get_satellite_ra_dec_delta( observer_loc, pos,
                                    &ra, &dec, &dist_to_satellite);
            epoch_of_date_to_j2000( jd, &ra, &dec);
            d_ra = (ra - target_ra + PI * 4.);
            while( d_ra > PI)
               d_ra -= PI + PI;
            d_dec = dec - target_dec;
            radius = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI;
            if( radius < search_radius)      /* good enough for us! */
               {
               double speed, posn_ang_of_motion;

               line1[16] = '\0';

               if( !header_line_shown)
                  {
                  printf( "NORAD  Int'l     RA (J2000) dec    Delta Radius  PA Speed\n");
                  header_line_shown = 1;
                  }
                                 /* Compute position one second later,  so we */
                                 /* can show speed/PA of motion: */
               t_since += TIME_EPSILON * 1440.;
               if( is_deep)
                  SDP4( t_since, &tle, sat_params, pos, NULL);
               else
                  SGP4( t_since, &tle, sat_params, pos, NULL);
               get_satellite_ra_dec_delta( observer_loc2, pos,
                                       &d_ra, &d_dec, &unused_delta2);
               epoch_of_date_to_j2000( jd, &d_ra, &d_dec);
               d_ra -= ra;
               d_dec -= dec;
               while( d_ra > PI)
                  d_ra -= PI + PI;
               while( d_ra < -PI)
                  d_ra += PI + PI;
               d_ra *= cos( dec);
               posn_ang_of_motion = atan2( d_ra, d_dec);
               if( posn_ang_of_motion < 0.)
                  posn_ang_of_motion += PI + PI;
               speed = sqrt( d_ra * d_ra + d_dec * d_dec) * 180. / PI;
                        /* Put RA into 0 to 2pi range: */
               ra = fmod( ra + PI * 10., PI + PI);
               printf( "%s %8.4f %8.4f %8.1f %5.2f %3d %5.2f\n",
                        line1 + 2, ra * 180. / PI, dec * 180. / PI,
                        dist_to_satellite, radius,
                        (int)(posn_ang_of_motion * 180 / PI),
                        speed * 60.);
                              /* "Speed" is displayed in arcminutes/second,
                                 or in degrees/minute */
               }
            }
         strcpy( line1, line2);
         }
   fclose( ifile);
   return( 0);
} /* End of main() */



================================================
FILE: obs_test.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

/*
    obs_test.cpp     23 September 2002

   (Revised slightly December 2012 to fix compiler warning errors.)

   An example 'main' function illustrating how to get topocentric
ephemerides for artificial satellites,  using the basic satellite
code plus the add-on topocentric functions.  The code reads the
file 'obs_test.txt',  getting commands setting the observer lat/lon
and altitude and time of observation.  When it gets a command
setting a particular JD,  it computes the topocentric RA/dec/dist
and prints them out.

   At present,  'obs_test.txt' sets up a lat/lon/height in Bowdoinham,
Maine,  corporate headquarters of Project Pluto,  and computes the
position of one low-orbit satellite (ISS) and one high-orbit satellite
(Cosmos 1966 rocket booster).  You should get :

Near-Earth type Ephemeris (SGP4) selected:
Object 25544U 98067A, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000
RA 350.1615 (J2000) dec -24.0241 dist 1867.97481 km
Deep-Space type Ephemeris (SDP4) selected:
Object 19448U 88076D, as seen from lat 44.01000 lon -69.90000, JD 2452541.50000
RA 3.5743 (J2000) dec 30.4293 dist 32114.83370 km
*/


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "norad.h"
#include "observe.h"

#define PI 3.141592653589793238462643383279

int main( const int argc, const char **argv)
{
   FILE *ifile = fopen( (argc == 1) ? "obs_test.txt" : argv[1], "rb");
   tle_t tle; /* Pointer to two-line elements set for satellite */
   char line1[100], line2[100];
   double lat = 0., lon = 0., ht_in_meters = 0., jd = 0.;
   int ephem = 1;       /* default to SGP4 */

   if( !ifile)
      {
      printf( "Couldn't open input OBS_TEST.TXT file\n");
      exit( -1);
      }
   if( fgets( line1, sizeof( line1), ifile))
      while( fgets( line2, sizeof( line2), ifile))
         {
         int err_val;

         if( !memcmp( line2, "Ephem ", 6))
            ephem = (line2[6] - '0');
         else if( !memcmp( line2, "JD ", 3))
            jd = atof( line2 + 3);
         else if( !memcmp( line2, "ht ", 3))
            ht_in_meters = atof( line2 + 3);
         else if( !memcmp( line2, "lat ", 4))
            lat = atof( line2 + 4) * PI / 180.;     /* cvt degrees to radians */
         else if( !memcmp( line2, "lon ", 4))
            lon = atof( line2 + 4) * PI / 180.;
         else if( (err_val = parse_elements( line1, line2, &tle)) >= 0)
            {                  /* hey! we got a TLE! */
            int is_deep = select_ephemeris( &tle);
            const char *ephem_names[5] = { "SGP ", "SGP4", "SGP8", "SDP4", "SDP8" };
            double sat_params[N_SAT_PARAMS], observer_loc[3];
            double rho_sin_phi, rho_cos_phi;
            double ra, dec, dist_to_satellite, t_since;
            double pos[3]; /* Satellite position vector */

            if( err_val)
               printf( "WARNING: TLE parsing error %d\n", err_val);
            earth_lat_alt_to_parallax( lat, ht_in_meters, &rho_cos_phi,
                                                          &rho_sin_phi);
            observer_cartesian_coords( jd, lon, rho_cos_phi, rho_sin_phi,
                        observer_loc);
            if( is_deep && (ephem == 1 || ephem == 2))
               ephem += 2;    /* switch to an SDx */
            if( !is_deep && (ephem == 3 || ephem == 4))
               ephem -= 2;    /* switch to an SGx */
            if( is_deep)
               printf("Deep-Space type Ephemeris (%s) selected:\n",
                                          ephem_names[ephem]);
            else
               printf("Near-Earth type Ephemeris (%s) selected:\n",
                                          ephem_names[ephem]);

            /* Calling of NORAD routines */
            /* Each NORAD routine (SGP, SGP4, SGP8, SDP4, SDP8)   */
            /* will be called in turn with the appropriate TLE set */
            t_since = (jd - tle.epoch) * 1440.;
            switch( ephem)
               {
               case 0:
                  SGP_init( sat_params, &tle);
                  err_val = SGP( t_since, &tle, sat_params, pos, NULL);
                  break;
               case 1:
                  SGP4_init( sat_params, &tle);
                  err_val = SGP4( t_since, &tle, sat_params, pos, NULL);
                  break;
               case 2:
                  SGP8_init( sat_params, &tle);
                  err_val = SGP8( t_since, &tle, sat_params, pos, NULL);
                  break;
               case 3:
                  SDP4_init( sat_params, &tle);
                  err_val = SDP4( t_since, &tle, sat_params, pos, NULL);
                  break;
               case 4:
                  SDP8_init( sat_params, &tle);
                  err_val = SDP8( t_since, &tle, sat_params, pos, NULL);
                  break;
               default:
                  printf( "? How did we get here? ephem = %d\n", ephem);
                  err_val = 0;
                  break;
               }
            if( err_val)
               printf( "Ephemeris error %d\n", err_val);
            line1[15] = '\0';
            printf( "Object %s, as seen from lat %.5f lon %.5f, JD %.5f\n",
                     line1 + 2, lat * 180. / PI, lon * 180. / PI, jd);
            get_satellite_ra_dec_delta( observer_loc, pos,
                                    &ra, &dec, &dist_to_satellite);
            epoch_of_date_to_j2000( jd, &ra, &dec);
            printf( "RA %.4f (J2000) dec %.4f dist %.5f km\n",
                        ra * 180. / PI, dec * 180. / PI, dist_to_satellite);
            }
         strcpy( line1, line2);
         }
   fclose( ifile);
   return( 0);
} /* End of main() */



================================================
FILE: obs_test.txt
================================================
#  Input file for 'obs_test'
#  Set up desired lat/lon/ht in meters/JD,  specify a TLE,  and
#  the topocentric RA/dec/distance will be shown.
lat 44.01
lon -69.9
# Western longitudes are negative,  eastern positive.  The above
# lat/lon corresponds to my location in Bowdoinham,  Maine,  in the
# northeastern United States.
ht 100
JD 2452541.5         /* 24 Sep 2002 0h UT */
ISS
1 25544U 98067A   02256.70033192  .00045618  00000-0  57184-3 0  1499
2 25544  51.6396 328.6851 0018421 253.2171 244.7656 15.59086742217834

# Above should give RA = 350.1615 deg, dec = -24.0241, dist = 1867.97542 km

# Now compute a second,  higher satellite for the same place/time:
Cosmos 1966 Rk
1 19448U 88076D   02255.52918163 -.00000002  00000-0  10000-3 0  4873
2 19448  65.7943 338.1906 7142558 193.4853 125.7046  2.04085818104610

# Above should give RA = 3.5743, dec = 30.4293, dist = 32114.83063 km


================================================
FILE: observe.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

#include <math.h>
#include "observe.h"

/*  Assorted functions useful in conjunction with the satellite code
   library for determining where the _observer_,  as well as the _target_,
   happens to be.  Combine the two positions,  and you can get the
   distance/RA/dec of the target as seen by the observer. */

#define PI 3.141592653589793238462643383279
#define EARTH_MAJOR_AXIS 6378140.
#define EARTH_MINOR_AXIS 6356755.
#define EARTH_AXIS_RATIO (EARTH_MINOR_AXIS / EARTH_MAJOR_AXIS)

      /* function for Greenwich sidereal time,  ripped from 'deep.cpp' */

static inline double ThetaG( double jd)
{
  /* Reference:  The 1992 Astronomical Almanac, page B6. */
  const double omega_E = 1.00273790934;
                   /* Earth rotations per sidereal day (non-constant) */
  const double UT = fmod( jd + .5, 1.);
  const double seconds_per_day = 86400.;
  const double jd_2000 = 2451545.0;   /* 1.5 Jan 2000 = JD 2451545. */
  double t_cen, GMST, rval;

  t_cen = (jd - UT - jd_2000) / 36525.;
  GMST = 24110.54841 + t_cen * (8640184.812866 + t_cen *
                           (0.093104 - t_cen * 6.2E-6));
  GMST = fmod( GMST + seconds_per_day * omega_E * UT, seconds_per_day);
  if( GMST < 0.)
     GMST += seconds_per_day;
  rval = 2. * PI * GMST / seconds_per_day;

  return( rval);
} /*Function thetag*/

void DLL_FUNC observer_cartesian_coords( const double jd, const double lon,
              const double rho_cos_phi, const double rho_sin_phi,
              double *vect)
{
   const double angle = lon + ThetaG( jd);

   *vect++ = cos( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.;
   *vect++ = sin( angle) * rho_cos_phi * EARTH_MAJOR_AXIS / 1000.;
   *vect++ = rho_sin_phi               * EARTH_MAJOR_AXIS / 1000.;
}

void DLL_FUNC earth_lat_alt_to_parallax( const double lat,
                    const double ht_in_meters,
                    double *rho_cos_phi, double *rho_sin_phi)
{
   const double u = atan( sin( lat) * EARTH_AXIS_RATIO / cos( lat));

   *rho_sin_phi = EARTH_AXIS_RATIO * sin( u) +
                           (ht_in_meters / EARTH_MAJOR_AXIS) * sin( lat);
   *rho_cos_phi = cos( u) + (ht_in_meters / EARTH_MAJOR_AXIS) * cos( lat);
}

void DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc,
                                 const double *satellite_loc, double *ra,
                                 double *dec, double *delta)
{
   double vect[3], dist2 = 0.;
   int i;

   for( i = 0; i < 3; i++)
      {
      vect[i] = satellite_loc[i] - observer_loc[i];
      dist2 += vect[i] * vect[i];
      }
   *delta = sqrt( dist2);
   *ra = atan2( vect[1], vect[0]);
   if( *ra < 0.)
      *ra += PI + PI;
   *dec = asin( vect[2] / *delta);
}

/* Formulae from Meeus' _Astronomical Algorithms_ for approximate precession.
More than accurate enough for our purposes.  */

static void precess( const double t_centuries, double *ra, double *dec)
{
   const double m = (3.07496 + .00186 * t_centuries / 2.) * (PI / 180.) / 240.;
   const double n = (1.33621 - .00057 * t_centuries / 2.) * (PI / 180.) / 240.;
   const double ra_rate  = m + n * sin( *ra) * tan( *dec);
   const double dec_rate = n * cos( *ra);

   *ra -= t_centuries * ra_rate * 100.;
   *dec -= t_centuries * dec_rate * 100.;
}

void DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec)
{
   const double t_centuries = (jd - 2451545.) / 36525.;

   precess( t_centuries, ra, dec);
}

void DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec)
{
   const double t_centuries = (jd - 2451545.) / 36525.;

   precess( -t_centuries, ra, dec);
}


================================================
FILE: observe.h
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

#ifdef _WIN32
#define DLL_FUNC __stdcall
#else
#define DLL_FUNC
#endif

#ifdef __cplusplus
extern "C" {
#endif

void DLL_FUNC earth_lat_alt_to_parallax( const double lat,
                    const double ht_in_meters,
                    double *rho_cos_phi, double *rho_sin_phi);
void DLL_FUNC observer_cartesian_coords( const double jd, const double lon,
              const double rho_cos_phi, const double rho_sin_phi,
              double *vect);
void DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc,
                                 const double *satellite_loc, double *ra,
                                 double *dec, double *delta);
void DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, double *dec);
void DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, double *dec);

#ifdef __cplusplus
}                       /* end of 'extern "C"' section */
#endif


================================================
FILE: out_comp.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

static int is_position_line( const char *buff)
{
   int rval = 0;

   if( strlen( buff) > 73 && buff[73] < ' ' &&
            buff[29] == '.' && buff[46] == '.' && buff[63] == '.')
      rval = 1;
   return( rval);
}

int main( const int argc, const char **argv)
{
   FILE *ifile1 = fopen( argv[1], "rb");
   FILE *ifile2 = fopen( argv[2], "rb");
   char buff1[80], buff2[80];
   int line = 0, n_valid = 0, worst_line = -1;
   double max_diff2 = 0.;

   if( !ifile1)
      printf( "%s not opened\n", argv[1]);
   if( !ifile2)
      printf( "%s not opened\n", argv[2]);
   if( argc < 3 || !ifile1 || !ifile2)
      {
      printf( "out_comp needs two files of output from test_sat to compare.\n");
      exit( -1);
      }

   while( fgets( buff1, sizeof( buff1), ifile1) &&
          fgets( buff2, sizeof( buff2), ifile2))
      {
      line++;
      if( is_position_line( buff1) && is_position_line( buff2))
         {
         double diff2 = 0., delta;
         int i;

         n_valid++;
         for( i = 22; i < 60; i += 17)
            {
            delta = atof( buff1 + i) - atof( buff2 + i);
            diff2 += delta * delta;
            }
         if( diff2 > max_diff2)
            {
            max_diff2 = diff2;
            worst_line = line;
            }
         }
      }
   fclose( ifile1);
   fclose( ifile2);
   printf( "%d lines read in; %d had positions\n", line, n_valid);
   printf( "Max difference: %.8f km at line %d\n",
                                  sqrt( max_diff2), worst_line);

   return( 0);
}


================================================
FILE: sat_code.def
================================================
LIBRARY sat_code
EXPORTS
   SGP_init                          @1
   SGP4_init                         @2
   SGP8_init                         @3
   SDP4_init                         @4
   SDP8_init                         @5
   SGP                               @6
   SGP4                              @7
   SGP8                              @8
   SDP4                              @9
   SDP8                              @10
   select_ephemeris                  @11
   parse_elements                    @12
   observer_cartesian_coords         @14
   get_satellite_ra_dec_delta        @15
   epoch_of_date_to_j2000            @16
   tle_checksum                      @17
   sxpx_set_implementation_param     @18
   sxpx_set_dpsec_integration_step   @19
   sxpx_library_version              @20
   j2000_to_epoch_of_date            @21


================================================
FILE: sat_eph.c
================================================
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <math.h>
#include <time.h>
#if defined( _WIN32) || defined( __WATCOMC__)
   #include "zlibstub.h"
#else
   #include <zlib.h>
#endif
#include "watdefs.h"
#include "afuncs.h"
#include "comets.h"
#include "date.h"
#include "norad.h"
#include "mpc_func.h"
#include "stringex.h"
#include "observe.h"

/* Code to generate topocentric ephemerides from TLE data,  mostly focussed
on the TLEs provided in https://www.github.com/Bill-Gray/tles. The program
can be compiled for standalone use or for use with the on-line artsat
ephemeris service at https://www.projectpluto.com/sat_eph.htm (q.v.). */

#define PI 3.1415926535897932384626433832795028841971693993751058209749445923

typedef struct
{
   double lat, lon, alt, rho_sin_phi, rho_cos_phi;
   double jd_start, jd_end, step_size;
   int n_steps;
   const char *desig;
} ephem_t;

static int verbose = 0;

static double ra_offset = 0., dec_offset = 0.;

static char *gzgets_trimmed( char *buff, const int buffsize, gzFile ifile)
{
   char *rval = gzgets( ifile, buff, buffsize);

   if( rval)
      {
      size_t i = 0;

      while( rval[i] != 10 && rval[i] != 13 && rval[i])
         i++;
      while( i && rval[i - 1] == ' ')
         i--;        /* drop trailing spaces */
      rval[i] = '\0';
      }
   return( rval);
}

static void show_base_60( char *buff, const unsigned n_millisec)
{
   snprintf( buff, 15, "%03u %02u %02u.%03u",
               n_millisec / 3600000u, (n_millisec / 60000u) % 60u,
               (n_millisec / 1000u) % 60u, n_millisec % 1000u);
}

static void put_ra_in_buff( char *buff, double ra)
{
   ra = fmod( ra, 2. * PI);
   if( ra < 0.)
      ra += PI + PI;
   show_base_60( buff, (unsigned)( 3600. * 1000. * ra * 12. / PI));
   memmove( buff, buff + 1, strlen( buff));     /* remove leading zero */
}

static void put_dec_in_buff( char *buff, const double dec)
{
   show_base_60( buff, (unsigned)( 3600. * 1000. * fabs( dec) * 180. / PI));
   *buff = (dec > 0. ? '+' : '-');
}

static double angle_between( const double *a, const double *b)
{
   const double cos_ang = dot_product( a, b) /
                              sqrt( dot_product( a, a) * dot_product( b, b));
   double rval = acose( cos_ang);

   return( rval * 180. / PI);
}

static inline bool desig_match( const tle_t *tle, const char *desig)
{
   size_t i = 0;
   bool rval = false;

   while( isdigit( desig[i]))
      i++;
   if( i == 5)
      {
      if( !desig[i])       /* desig is all digits -> it's the NORAD # */
         rval = (atoi( desig) == tle->norad_number);
      else
         {
         i = strlen( desig);
         if( i > 5 && i < 9)
            rval = !memcmp( tle->intl_desig, desig, i) && tle->intl_desig[i] <= ' ';
         }
      }
   return( rval);
}

/* Generates unit vector in the direction of ivect and stores it in z_vect;
a unit vector perpendicular to that in the xy plane,  stored in x_vect;
and a unit vector perpendicular to both,  stored in y_vect.   */

static double make_orthogonal_basis( const double *ivect,
              double *x_vect, double *y_vect, double *z_vect)
{
   double rval, len;

   memcpy( z_vect, ivect, 3 * sizeof( double));
   rval = normalize_vect3( z_vect);
   len = hypot( z_vect[0], z_vect[1]);
   x_vect[0] = z_vect[1] / len;
   x_vect[1] = -z_vect[0] / len;
   x_vect[2] = 0.;
   vector_cross_product( y_vect, z_vect, x_vect);
   return( rval);
}

/* 'obs_pos' = observer position relative to the geocenter, km
   'topo_posn' = artsat position relative to the observer, km
   'sat_vel' = artsat vel, km/minute (usual,  though somewhat oddball,  SxPx units)
   '*motion_pa' = returned posn angle of motion,  degrees
   apparent total angular motion is returned               */

static double compute_angular_rates( const double *obs_pos, const double *topo_posn,
              const double *sat_vel, double *motion_pa,
              double *ra_motion, double *dec_motion)
{
   double vel[3];         /* velocity of sat relative to observer */
   double x_vect[3];    /* unit vector in equatorial plane perpendicular to topo_posn */
   double y_vect[3];    /* unit vector perpendicular to topo_posn & x_vect */
   double z_vect[3];    /* unit vector version of topo_posn */
   double xmotion, ymotion, total_motion, dist;
   const double omega_E = 1.00273790934;
                   /* Earth rotations per sidereal day (non-constant) */
   const double omega = omega_E * 2. * PI / minutes_per_day;
                   /* Earth rotational rate in radians/minute */

   vel[0] = sat_vel[0] + omega * obs_pos[1];
   vel[1] = sat_vel[1] - omega * obs_pos[0];
   vel[2] = sat_vel[2];
   dist = make_orthogonal_basis( topo_posn, x_vect, y_vect, z_vect);
   xmotion = dot_product( vel, x_vect) / dist;      /* all in radians/minute */
   ymotion = dot_product( vel, y_vect) / dist;
   total_motion = hypot( xmotion, ymotion);
   *motion_pa = PI + atan2( xmotion, ymotion);
   *motion_pa *= 180. / PI;
   *ra_motion = -xmotion * (180. / PI) * 60.;
   *dec_motion = -ymotion * (180. / PI) * 60.;
   return( total_motion * (180. / PI) * 60.);      /* cvt to arcmin/min = arcsec/sec */
}

static char _header[200];
static int motion_units = 1;     /* default to '/minute = degrees/hr = "/sec */
static bool show_separate_motions = false;
static bool output_state_vectors = false;
static bool output_mjd = false;

static int show_ephems_from( const char *path_to_tles, const ephem_t *e,
                                  const char *filename, int start_line)
{
   gzFile ifile;
   char line0[100], line1[100], line2[100];
   int show_it = 1, header_shown = 0;
   double jd_tle = 0., tle_range = 1e+10, abs_mag = 0.;
   const bool is_geocentric = (e->rho_sin_phi == 0. && e->rho_cos_phi == 0.);
   static const char *header_text =
           "Date (UTC)  Time       R.A. (J2000)  decl   Azim   Alt  Elong"
           "  LuElo  Dist(km) \"/sec     PA";
   static const char *geo_header_text =
           "Date (UTC)  Time       R.A. (J2000)  decl   Elong  LuElo  Dist(km) \"/sec     PA";

   if( verbose)
      printf( "Should examine '%s'; start line %d\n", filename, start_line);
   snprintf( line0, sizeof( line0), "%s/%s", path_to_tles, filename);
   ifile = gzopen( line0, "rb");
   if( !ifile)       /* maybe it's compressed */
      {
      strlcat_error( line0, ".gz");
      ifile = gzopen( line0, "rb");
      }
   if( !ifile)
      {
      fprintf( stderr, "'%s' not opened\n", line0);
      exit( 0);
      }
   *line0 = *line1 = '\0';
   while( gzgets_trimmed( line2, sizeof( line2), ifile))
      {
      tle_t tle;

      if( *line2 == '#')
         {
         char *tptr = strstr( line2, " H ");

         if( !memcmp( line2, "# MJD ", 6))
            {
            jd_tle = atof( line2 + 6) + 2400000.5;
            tle_range = 1.;
            show_it = (jd_tle < e->jd_end && jd_tle + tle_range > e->jd_start);
            }
         else if( tptr)
            {
            abs_mag = atof( tptr + 2);
            if( verbose)
               printf( "H = %.3f\n", abs_mag);
            }
         }
      else if( show_it && parse_elements( line1, line2, &tle) >= 0
                     && desig_match( &tle, e->desig))
         {
         double sat_params[N_SAT_PARAMS], jd = e->jd_start;
         size_t i, j;
         const int is_deep_type = select_ephemeris( &tle);

         if( is_deep_type)
            SDP4_init( sat_params, &tle);
         else
            SGP4_init( sat_params, &tle);
         if( verbose > 1)
            {
            printf( "Got TLEs for %f :\n", jd);
            printf( "%s\n%s\n%s\n", line0, line1, line2);
            }
         for( i = 0; i < (size_t)e->n_steps; i++,
                                         jd = e->jd_start + (double)i * e->step_size)
            if( (int)i >= start_line && jd >= jd_tle && jd < jd_tle + tle_range)
               {
               char buff[90], dec_buff[20], ra_buff[20], alt_buff[17];
               double pos[3], vel[3], obs_pos[3], ra, dec, dist;
               const double t_since = (jd - tle.epoch) * minutes_per_day;
               double solar_xyzr[4], lunar_xyzr[4], topo_posn[3], elong;
               double motion_rate, motion_pa;
               double ra_motion, dec_motion;
               const char *format_string;

               if( !header_shown)
                  {
                  char *tptr;

                  header_shown = 1;
                  printf( "\nEphemerides for %05d = %s%.2s-%s\n",
                              tle.norad_number,
                              (atoi( tle.intl_desig) > 57000) ? "19" : "20",
                              tle.intl_desig, tle.intl_desig + 2);
                  tptr = line0;
                  if( *tptr == '0' && tptr[1] == ' ')
                     tptr += 2;    /* actually a '3LE' with name prefaced by '0 ' */
                  snprintf( _header, sizeof( _header),
                          "%s\n%s", tptr, (is_geocentric ? geo_header_text : header_text));
                  if( show_separate_motions)
                     strcat( _header, "    RA \"/sec  dec");
                  if( motion_units == 60)
                     while( NULL != (tptr = strstr( _header, "/sec ")))
                        memcpy( tptr, "/min", 4);
                  strcat( _header, abs_mag ? "      Mag\n" : "\n");
                  if( output_state_vectors)
                     strcpy( _header, "Date (UTC)  Time"
                               "          x            y            z"
                               "          vx           vy           vz\n");
                  printf( "%s", _header);
                  }
               if( output_mjd)
                  snprintf( buff, sizeof( buff), "%.5f", jd - 2400000.5);
               else
                  full_ctime( buff, jd, FULL_CTIME_YMD | FULL_CTIME_MONTHS_AS_DIGITS
                                 | FULL_CTIME_LEADING_ZEROES);
               if( is_deep_type)
                  SDP4( t_since, &tle, sat_params, pos, vel);
               else
                  SGP4( t_since, &tle, sat_params, pos, vel);
               observer_cartesian_coords( jd, e->lon, e->rho_cos_phi,
                                        e->rho_sin_phi, obs_pos);
               get_satellite_ra_dec_delta( obs_pos, pos, &ra, &dec, &dist);
               epoch_of_date_to_j2000( jd, &ra, &dec);
               if( output_state_vectors)
                  {
                  const double year = 2000. + (jd - 2451545.) / 365.25;
                  double matrix[9];

                  setup_precession( matrix, year, 2000.);
                  precess_vector( matrix, pos, pos);
                  precess_vector( matrix, vel, vel);
                  printf( "%s %14.5f%14.5f%14.5f%11.5f%11.5f%11.5f\n",
                              buff, pos[0], pos[1], pos[2],
                              vel[0] / 60., vel[1] / 60., vel[2] / 60.);
                  }
               ra += ra_offset;
               dec += dec_offset;
               put_ra_in_buff( ra_buff, ra);
               put_dec_in_buff( dec_buff, dec);
               ra_buff[10] = dec_buff[9] = '\0';
               for( j = 0; j < 3; j++)
                  topo_posn[j] = pos[j] - obs_pos[j];
               motion_rate = compute_angular_rates( obs_pos, topo_posn, vel, &motion_pa,
                           &ra_motion, &dec_motion);
               lunar_solar_position( jd, lunar_xyzr, solar_xyzr);
               ecliptic_to_equatorial( solar_xyzr);
               ecliptic_to_equatorial( lunar_xyzr);
               if( !is_geocentric)
                  {
                  double x_vect[3], y_vect[3], z_vect[3], alt, az;

                  make_orthogonal_basis( obs_pos, x_vect, y_vect, z_vect);
                  az = PI + atan2( dot_product( x_vect, topo_posn),
                                   dot_product( y_vect, topo_posn));
                  az *= 180. / PI;
                  alt = 90. - angle_between( topo_posn, obs_pos);
                  snprintf( alt_buff, sizeof( alt_buff), " %5.1f %+05.1f",
                              az,  alt);
                  }
               else
                  *alt_buff = '\0';
               elong = angle_between( topo_posn, solar_xyzr);
               if( !output_state_vectors)
                  {
                  printf( "%s  %s  %s%s %6.1f %6.1f %8.0f", buff, ra_buff, dec_buff,
                     alt_buff, elong, angle_between( topo_posn, lunar_xyzr), dist);
                  motion_rate *= (double)motion_units;
                  if( motion_rate < 9.999)
                     format_string = "  %6.4f %6.1f";
                  else if( motion_rate < 99.99)
                     format_string = "  %6.3f %6.1f";
                  else if( motion_rate < 999.9)
                     format_string = "  %6.2f %6.1f";
                  else if( motion_rate < 9999.)
                     format_string = "  %6.1f %6.1f";
                  else
                     format_string = "  %6.0f %6.1f";
                  printf( format_string, motion_rate, motion_pa);
                  if( show_separate_motions)
                     {
                     const char precision = format_string[5];

                     snprintf( buff, sizeof( buff),
                                 "  %%+7.%cf %%+7.%cf", precision, precision);
                     printf( buff, ra_motion * (double)motion_units,
                                  dec_motion * (double)motion_units);
                     }

                  if( !abs_mag)
                     printf( "\n");
                  else
                     {
                     const double phase_ang = (180. - elong) * (PI / 180.);
                     double mag = abs_mag + 5. * log10( dist / AU_IN_KM)
                              + phase_angle_correction_to_magnitude(
                                       phase_ang, 0.15);
                     printf( "%8.1f\n", mag);
                     }
                  }
               start_line = (int)i + 1;
               }
         }
      strcpy( line0, line1);
      strcpy( line1, line2);
      }
   gzclose( ifile);
   return( start_line);
}

static const char *tle_list_filename = "tle_list.txt";

int generate_artsat_ephems( const char *path_to_tles, const ephem_t *e)
{
   gzFile ifile;
   char buff[100];
   int is_in_range = 0, id_matches = 1, start_line = 0;

   snprintf( buff, sizeof( buff), "%s/%s", path_to_tles, tle_list_filename);
   if( verbose > 1)
      printf( "Opening '%s', looking for '%s'\n", buff, e->desig);
   ifile = gzopen( buff, "rb");
   if( !ifile)
      {
      strlcat_error( buff, ".gz");
      ifile = gzopen( buff, "rb");
      }
   if( !ifile)
      {
      fprintf( stderr, "'%s' not opened\n", buff);
      exit( 0);
      }
   while( start_line != e->n_steps &&
                          gzgets_trimmed( buff, sizeof( buff), ifile))
      {
      if( !memcmp( buff, "# Range:", 8))
         {
         char t_start[40], t_end[40];
         int n_scanned = sscanf( buff + 8, "%39s %39s", t_start, t_end);

         assert( 2 == n_scanned);
         if( get_time_from_string( 0., t_start, FULL_CTIME_YMD, NULL) < e->jd_end
                  && get_time_from_string( 0., t_end, FULL_CTIME_YMD, NULL) > e->jd_start)
            is_in_range = 1;
         }
      if( !memcmp( buff, "# ID:", 5))
         {
         int i;

         if( buff[5] != ' ' || buff[11] != ' ' || buff[12] != ' ')
            fprintf( stderr, "BAD LINE %s\n", buff);
         for( i = 6; i < 10; i++)
            if( !isdigit( buff[i]) || !isdigit( buff[i + 7]))
               {
               printf( "BAD LINE (2) %s\n", buff);
               i = 99;
               }
         if( strcmp( e->desig, buff + 13) && atoi( buff + 5) != atoi( e->desig))
            id_matches = 0;
         }
      if( !memcmp( buff, "# Include ", 10))
         {
         if( is_in_range && id_matches)
            start_line = show_ephems_from( path_to_tles, e, buff + 10, start_line);
         is_in_range = 0;
         id_matches = 1;
         }
      }
   gzclose( ifile);
   if( start_line)
      printf( "%s", _header);
   return( start_line);
}

static int set_location( ephem_t *e, const char *mpc_code, const char *obscode_file_name)
{
   mpc_code_t c;
   int rval = get_lat_lon_info( &c, mpc_code);

   if( rval)
      {
      gzFile ifile = gzopen( obscode_file_name, "rb");
      char buff[200];

      if( !ifile)
         {
         fprintf( stderr, "'%s' not found\n", obscode_file_name);
         exit( 0);
         }
      while( rval && gzgets_trimmed( buff, sizeof( buff), ifile))
         if( !memcmp( mpc_code, buff, 3))
            {
            const int planet = get_mpc_code_info( &c, buff);

            if( planet != 3)
               {
               fprintf( stderr, "MPC code '%s' is for planet %d\n",
                           mpc_code, planet);
               exit( 0);
               }
            rval = 0;
            printf( "%s\n", c.name);
            }
      gzclose( ifile);
      }
   if( !rval)
      {
      e->lat = c.lat;
      e->lon = c.lon;
      e->alt = c.alt;
      e->rho_cos_phi = c.rho_cos_phi;
      e->rho_sin_phi = c.rho_sin_phi;
      if( c.lon > PI)
         c.lon -= PI + PI;
      if( c.rho_sin_phi || c.rho_cos_phi)
         printf( "Latitude %c %f, Longitude %c %f\nAltitude %.1f meters (above WGS84 ellipsoid)\n",
                  (c.lat > 0. ? 'N' : 'S'), fabs( c.lat) * 180. / PI,
                  (c.lon > 0. ? 'E' : 'W'), fabs( c.lon) * 180. / PI,
                  c.alt);
      }
   return( rval);
}

#ifdef ON_LINE_VERSION
   #define OBSCODES_DOT_HTML_FILENAME  "/home/projectp/public_html/cgi-bin/fo/ObsCodes.htm"
   #define ROVERS_DOT_TXT_FILENAME     "/home/projectp/public_html/cgi-bin/fo/rovers.txt"
   #define PATH_TO_TLES                "/home/projectp/public_html/tles"
#else
   #define OBSCODES_DOT_HTML_FILENAME  "/home/phred/.find_orb/ObsCodes.htm"
   #define ROVERS_DOT_TXT_FILENAME     "/home/phred/.find_orb/rovers.txt"
   #define PATH_TO_TLES                "/home/phred/tles"
#endif

static const char *get_arg( const char **argv)
{
   const char *rval;

   if( argv[0][0] == '-' && argv[0][1])
      {
      if( !argv[0][2] && argv[1])
         rval = argv[1];
      else
         rval = argv[0] + 2;
      }
   else
      rval = NULL;
   if( !rval)
      {
      fprintf( stderr, "Can't get an argument : '%s'\n", argv[0]);
      exit( 0);
      }
   return( rval);
}

static void fix_desig( char *desig)
{
   size_t i;
   int bitmask = 0;

   for( i = 0; i < 10 && desig[i]; i++)
      if( isdigit( desig[i]))
         bitmask |= (1 << (int)i);
   if( i >= 9 && bitmask == 0xef && desig[4] == '-')
      {
      desig[0] = desig[2];                   /* it's in YYYY-NNNA form; */
      desig[1] = desig[3];                   /* cvt to YYNNNA form */
      for( i = 5; desig[i - 1]; i++)
         desig[i - 3] = desig[i];
      }
}

static void error_help( void)
{
   printf( "'sat_eph' arguments:\n"
           "   -c(MPC code) : specify location (default = geocentric)\n"
           "   -t(date/time) : starting time of ephemeris (default = now)\n"
           "   -n(#) : number of ephemeris steps (default = 20)\n"
           "   -s(#) : ephemeris step size in days (default = 1h)\n"
           "   -S    : show motions in RA/dec components,  as well as total/PA\n");
   printf( "   -o(#) : five digit NORAD number or YYNNNA international designation\n"
           "   -r    : do _not_ round times to nearest step size\n"
           "   -u    : show motions in \"/min = degrees/hr (default is \"/sec)\n"
           "   -m    : show times as MJD\n"
           "   -V    : output state vectors instead of observables\n"
           "   -v(#) : level of verbosity\n");
}

int dummy_main( const int argc, const char **argv)
{
   int i;
   ephem_t e;
   bool round_to_nearest_step = true;
   const char *mpc_code = "500";
   const char *override_tle_filename = NULL;

   if( argc < 2)
      {
      error_help( );
      return( 0);
      }
   memset( &e, 0, sizeof( ephem_t));
   e.jd_start = current_jd( );
   e.n_steps = 20;
   e.step_size = 1. / 24.;
   for( i = 1; i < argc; i++)
      if( argv[i][0] == '-' && argv[i][1])
         {
         const char *arg = get_arg( argv + i);

         switch( argv[i][1])
            {
            case 'c':
               mpc_code = arg;
               break;
            case 'f':
               tle_list_filename = arg;
               break;
            case 'F':
               override_tle_filename = arg;
               break;
            case 't':
               e.jd_start = get_time_from_string( e.jd_start, arg, FULL_CTIME_YMD, NULL);
               break;
            case 'm':
               output_mjd = true;
               break;
            case 'n':
               e.n_steps = atoi( arg);
               break;
            case 'O':
               if( sscanf( arg, "%lf,%lf", &ra_offset, &dec_offset) == 2)
                  {
                  printf( "Offsetting by %f degrees in RA,  %f degrees in dec\n",
                                 ra_offset, dec_offset);
                  ra_offset *= PI / 180.;
                  dec_offset *= PI / 180.;
                  }
               break;
            case 'r':
               round_to_nearest_step = false;
               break;
            case 's':
               if( arg && *arg)
                  {
                  const char end_char = arg[strlen( arg) - 1];

                  e.step_size = atof( arg);
                  switch( end_char)
                     {
                     case 'h':
                        e.step_size /= hours_per_day;
                        break;
                     case 'm':
                        e.step_size /= minutes_per_day;
                        break;
                     case 's':
                        e.step_size /= seconds_per_day;
                        break;
                     }
                  }
               break;
            case 'S':
               show_separate_motions = true;
               break;
            case 'u':
               motion_units = 60;
               break;
            case 'o':
               /* Will handle below */
               break;
            case 'v':
               verbose = 1 + atoi( arg);
               break;
            case 'V':
               output_state_vectors = 1;
               break;
            default:
               fprintf( stderr, "Unrecognized option '%s'\n", argv[i]);
               error_help( );
               return( 0);
            }
         }
   if( set_location( &e, mpc_code, OBSCODES_DOT_HTML_FILENAME))
      if( set_location( &e, mpc_code, ROVERS_DOT_TXT_FILENAME))
         fprintf( stderr, "WARNING: Could not parse location '%s'\n", mpc_code);
   if( round_to_nearest_step && e.step_size)
      e.jd_start = floor( (e.jd_start - 0.5) / e.step_size) * e.step_size + 0.5;
   e.jd_end   = e.jd_start + (double)e.n_steps * e.step_size;
   if( verbose)
      printf( "arguments parsed;  JDs %f to %f\n", e.jd_start, e.jd_end);
   for( i = 1; i < argc; i++)
      if( argv[i][0] == '-' && argv[i][1] == 'o')
         {
         char desig[30];

         strncpy( desig, get_arg( argv + i), 29);
         fix_desig( desig);
         e.desig = desig;
         if( override_tle_filename)
            show_ephems_from( PATH_TO_TLES, &e, override_tle_filename, 0);
         else
            generate_artsat_ephems( PATH_TO_TLES, &e);
         }
   return( 0);
}

#ifndef ON_LINE_VERSION
int main( const int argc, const char **argv)
{
   return( dummy_main( argc, argv));
}
#else

#include <errno.h>
#ifdef __has_include
   #if __has_include(<cgi_func.h>)
       #include "cgi_func.h"
   #else
       #error   \
         'cgi_func.h' not found.  This project depends on the 'lunar'\
         library.  See www.github.com/Bill-Gray/lunar .\
         Clone that repository,  'make'  and 'make install' it.
   #endif
#else
   #include "cgi_func.h"
#endif

int main( const int unused_argc, const char **unused_argv)
{
   const char *argv[2000];
   const size_t max_buff_size = 40000;       /* room for 500 obs */
   char *buff = (char *)malloc( max_buff_size);
   char field[30], time_text[80];
   char num_steps[30], step_size[30], obs_code[40];
   FILE *lock_file = fopen( "lock.txt", "w");
   int cgi_status, i, argc = 9;
   bool round_step = false;
#ifndef _WIN32
   extern char **environ;

   avoid_runaway_process( 15);
#endif         /* _WIN32 */
   setbuf( lock_file, NULL);
   INTENTIONALLY_UNUSED_PARAMETER( unused_argc);
   INTENTIONALLY_UNUSED_PARAMETER( unused_argv);
   printf( "Content-type: text/html\n\n");
   printf( "<html> <body> <pre>\n");
   if( !lock_file)
      {
      printf( "<p> Server is busy.  Try again in a minute or two. </p>");
      printf( "<p> Your artsat ephemerides are very important to us! </p>");
      return( 0);
      }
   fprintf( lock_file, "We're in\n");
   *time_text = *num_steps = *step_size = *obs_code = '\0';
#ifndef _WIN32
   for( i = 0; environ[i]; i++)
      fprintf( lock_file, "%s\n", environ[i]);
#endif
   cgi_status = initialize_cgi_reading( );
   fprintf( lock_file, "CGI status %d\n", cgi_status);
   if( cgi_status <= 0)
      {
      printf( "<p> <b> CGI data reading failed : error %d </b>", cgi_status);
      printf( "This isn't supposed to happen.</p>\n");
      return( 0);
      }
   while( !get_cgi_data( field, buff, NULL, max_buff_size))
      {
      fprintf( lock_file, "Field '%s'\n", field);
      if( !strcmp( field, "time") && strlen( buff) < sizeof( time_text))
         strcpy( time_text, buff);
      if( !strcmp( field, "obj_name"))
         {
         char *obj_name = (char *)malloc( strlen( buff) + 1);

         strcpy( obj_name, buff);
         argv[argc++] = "-o";
         argv[argc++] = obj_name;
         }
      else if( !memcmp( field, "obj_", 4))      /* selected an object check-box */
         {
         char *obj_name = (char *)malloc( strlen( field) - 3);

         strcpy( obj_name, field + 4);
         argv[argc++] = "-o";
         argv[argc++] = obj_name;
         }
      else if( !strcmp( field, "motion60"))
         motion_units = 60;
      if( !strcmp( field, "num_steps") && strlen( buff) < sizeof( num_steps))
         strcpy( num_steps, buff);
      if( !strcmp( field, "step_size") && strlen( buff) < sizeof( step_size))
         {
         char *tptr = strchr( buff, 'v');

         if( tptr)
            {
            verbose = atoi( tptr + 1);
            *tptr = '\0';
            }
         strcpy( step_size, buff);
         }
      if( !strcmp( field, "obs_code") && strlen( buff) < sizeof( obs_code))
         strcpy( obs_code, buff);
      if( !strcmp( field, "round_step"))
         round_step = true;
      if( !strcmp( field, "vectors"))
         output_state_vectors = true;
      if( !strcmp( field, "mjd"))
         output_mjd = true;
      if( !strcmp( field, "show_separate_motions"))
         argv[argc++] = "-S";
      }
   fprintf( lock_file, "Fields read\n");
   if( !round_step)
      argv[argc++] = "-r";
   argv[0] = "sat_eph";
   argv[1] = "-t";
   argv[2] = time_text;
   argv[3] = "-c";
   argv[4] = obs_code;
   argv[5] = "-n";
   argv[6] = num_steps;
   argv[7] = "-s";
   argv[8] = step_size;
   argv[argc] = NULL;
   for( i = 0; argv[i]; i++)
      fprintf( lock_file, "arg %d: '%s'\n", (int)i, argv[i]);
   dummy_main( argc, argv);
   fprintf( lock_file, "dummy_main called\n");
   free( buff);
   printf( "On-line Sat_eph compiled " __DATE__ " " __TIME__ " UTC-5h\n");
   printf( "See <a href='https://www.github.com/Bill-Gray/sat_code'>"
               "https://www.github.com/Bill-Gray/sat_code</a> for source code\n");
   printf( "</pre> </body> </html>");
   return( 0);
}
#endif


================================================
FILE: sat_id.cpp
================================================
/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */

/*
    sat_id.cpp     8 March 2003,  with updates as listed below

   An example 'main' function illustrating how to find which satellite(s)
are within a given radius of a given RA/dec,  as seen from a given
point.  The code reads in a file of observations in MPC format (name
provided as the first command-line argument).  For example:

sat_id mpc_obs.txt

   would hunt through the file 'mpc_obs.txt' for MPC-formatted
observations.  It would then read the file 'alldat.tle',  looking
for corresponding satellites within .2 degrees of said observations.
It then spits out the original file,  with satellite IDs added
(when found) after each observation line.  For each IDed satellite,
the international and NORAD designations are given,  along with
its angular distance from the search point,  position angle of
motion,  and apparent angular rate of motion in arcminutes/second
(or,  equivalently,  degrees/minute). */

/* 2 July 2003:  fixed the day/month/year to JD part of 'get_mpc_data()'
so it will work for all years >= 0 (previously,  it worked for years
2000 to 2099... plenty for the practical purpose of ID'ing recently-found
satellites,  but this is also an 'example' program.) */

/* 3 July 2005:  revised the check on the return value for parse_elements().
Now elements with bad checksums won't be rejected. */

/* 23 June 2006:  after comment from Eric Christensen,  revised to use
names 'ObsCodes.html' or 'ObsCodes.htm',  with 'stations.txt' being a
third choice.  Also added the '-a' command line switch to cause the program
to show all lines from input (default is now that only MPC astrometric
input gets echoed.)   */

/* 30 June 2006:  further comment from Eric Christensen:  when computing
object motion from two consecutive observations,  if the second one has
a date/time preceding the first,  you get a negative rate of motion that's
off by 180 degrees.  Fixed this. */

/* 17 Nov 2006:  artificial satellite data is now being provided in a
file named 'ALL_TLE.TXT'.  I've modified the default TLE to match.
(Note : since modified to be read from 'tle_list.txt',  as found in
the https://www.github.com/Bill-Gray/tles repository.  This allows
for multiple TLEs to be read.) */

/* 22 Oct 2012:  minor cosmetic changes,  such as making constant variables
of type 'const',  updating URL for the MPC station code file,  adding a
comment or two. */

/* 7 Jan 2013:  revised output to show satellite name if available,  plus
the eccentricity,  orbital period,  and inclination. */

/* 2013 Dec 8:  revised to pay attention to "# MJD" and "#Ephem start"
lines,  for files that contain many TLEs covering different time spans
for the same object.  I sometimes create such files;  when that happens,
for each observation,  only the TLE(s) covering that observation's time
should be used,  and the others are suppressed.       */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <assert.h>
#if defined( _WIN32) || defined( __WATCOMC__)
   #include <malloc.h>     /* for alloca() prototype */
   #include "zlibstub.h"
#else
   #include <unistd.h>
   #include <zlib.h>
#endif

#if defined(_MSC_VER) && _MSC_VER < 1900
                      /* For older MSVCs,  we have to supply our own  */
                      /* snprintf().  See snprintf.cpp for details.  */
int snprintf( char *string, const size_t max_len, const char *format, ...);
#endif

#if defined( __has_include)
   #if !__has_include(<watdefs.h>)
      #error   \
        "You need the 'lunar' library (https://www.github.com/Bill-Gray/lunar).\
 See https://github.com/Bill-Gray/sat_code/issues/2 for a fix to this."
            /* This would normally be followed by dozens of errors.  GCC, */
            /* at least,  stops completely when it can't find a system */
            /* include file.  */
      #include <stop_cascading_errors>
   #endif
#endif

#include "norad.h"
#include "observe.h"
#include "watdefs.h"
#include "mpc_func.h"
#include "afuncs.h"
#include "date.h"
#include "sat_util.h"
#include "stringex.h"

#define OBSERVATION struct observation

OBSERVATION
   {
   char text[81];
   double jd, ra, dec;
   double observer_loc[3];
   };

typedef struct
{
   double dist, ra, dec, motion_rate, motion_pa;
   int norad_number;
   char intl_desig[9];
   char text[80];
   bool in_shadow;
} match_t;

typedef struct
{
   OBSERVATION *obs;
   size_t idx1, idx2, n_obs, n_matches;
   double speed;
   match_t *matches;
} object_t;

/* When we encounter a line for a spacecraft-based observation's offset
from the geocenter (a "second line" in the MPC's punched-card format system),
we store that offset in an offset_t structure.  Once the file has been
read in,  we go back and match the offsets to their corresponding observations.
If the lines aren't in proper order (which happens),  we'll fix it,  and
can detect errors where we get one line but not the other. */

typedef struct
{
   double jd, posn[3];
   char mpc_code[4];
} offset_t;

#define PI 3.1415926535897932384626433832795028841971693993751058209749445923
#define TIME_EPSILON (1./86400.)
#define EARTH_MAJOR_AXIS    6378137.
#define EARTH_MINOR_AXIS    6356752.
#define is_power_of_two( X)   (!((X) & ((X) - 1)))

/* By default,  Sat_ID checks for possible matches with both artificial
objects (its main use) and some natural irregular satellites of the gas
giants.  Use the '-n0' command line option to identify only artificial
objects,  or '-n1' to identify only the natural irregular objects. */

#define IDENTIFY_ALL             2
#define IDENTIFY_ARTSATS         0
#define IDENTIFY_NATSATS         1

static int identify_filter = IDENTIFY_ALL;

/* For testing purposes,  I sometimes want _only_ "my" TLEs (not the
   'classified' or Space-Watch TLEs) to be used.  This is invoked with -s. */
static bool my_tles_only = false;

#if !defined( ON_LINE_VERSION) && !defined( _WIN32)
   #define CSI "\x1b["
   #define OSC "\x1b]"
   #define REVERSE_VIDEO      CSI "0m" CSI "7m"
   #define NORMAL_VIDEO       CSI "0m"
#else
   #define REVERSE_VIDEO
   #define NORMAL_VIDEO
#endif

/* Sputnik 1 was launched on 1957 Oct 4.  There is no point in
checking for satellites before that date. TLEs,  and therefore
this program,  won't work after 2057 Jan 1. */

const double oct_4_1957 = 2436115.5;
const double jan_1_2057 = 2472364.5;

static int get_mpc_data( OBSERVATION *obs, const char *buff)
{
   obs->jd = extract_date_from_mpc_report( buff, NULL);
   if( obs->jd < oct_4_1957 || obs->jd > jan_1_2057)
      return( -1);            /* not an 80-column MPC record */
   if( get_ra_dec_from_mpc_report( buff, NULL, &obs->ra, NULL,
                                     NULL, &obs->dec, NULL))
      if( 's' != buff[14] && 'v' != buff[14])   /* satellite offsets and */
         return( -2);          /* roving obs lat/lons won't have RA/decs */
   assert( strlen( buff) < sizeof( obs->text));
   strncpy( obs->text, buff, sizeof( obs->text));
   obs->text[80] = '\0';
   return( 0);
}

static int extract_csv_value( char *obuff, const char *ibuff, int idx, size_t obuff_len)
{
   while( idx && *ibuff)
      if( *ibuff++ == ',')
         idx--;
   if( !*ibuff)
      return( -1);
   if( *ibuff == '"')
      ibuff++;
   obuff_len--;
   while( obuff_len-- && *ibuff && *ibuff != ',' && *ibuff != '"')
      *obuff++ = *ibuff++;
   *obuff = '\0';
   return( 0);
}

/* If we encounter 'field' data -- we're determining which artsats
are in a field,  not which ones match moving objects -- the output
is very different : */

static bool field_mode = false;

/* An imaging field is specified as an image name,  date/time, RA and
dec in decimal degrees,  and MPC obscode,  all comma-separated,  such as

MyImage,2023 nov 17 03:14:15.9,271.818,-14.142,T05
"Another image",2023-11-17.314159,21 16 58.21,"+31 41 59.26","V00"

In the first,  the RA and dec are in decimal degrees;  in the second,
sexagesimal notation is used and the RA is assumed to be in hours,
minutes,  and seconds.  You need the commas,  but can skip the
quotation marks.     */

static int get_field_data( OBSERVATION *obs, const char *buff)
{
   int rval = -1;
   size_t n_commas, i;

   for( i = n_commas = 0; buff[i] && n_commas < 5; i++)
      if( buff[i] == ',')
         n_commas++;
   if( n_commas == 4 && strlen( buff) < 80)
      {
      char tbuff[80];
      int bytes_read;

      extract_csv_value( tbuff, buff, 1, sizeof( tbuff));
      obs->jd = get_time_from_string( 0., tbuff, 0, NULL);
      if( obs->jd > oct_4_1957 && obs->jd < jan_1_2057)
         {
         extract_csv_value( tbuff, buff, 4, sizeof( tbuff));
         if( strlen( tbuff)
Download .txt
gitextract_zy817hdm/

├── .github/
│   └── workflows/
│       └── github_actions_build.yml
├── .gitignore
├── LICENSE
├── README.md
├── basics.cpp
├── common.cpp
├── deep.cpp
├── dropouts.c
├── dynamic.cpp
├── elem2tle.cpp
├── fake_ast.cpp
├── fix_tles.cpp
├── get_el.cpp
├── get_high.cpp
├── get_vect.cpp
├── line2.cpp
├── makefile
├── mergetle.cpp
├── msvc.mak
├── msvc_dll.mak
├── norad.h
├── norad_in.h
├── nu2vect.c
├── nu_readme.txt
├── obs_tes2.cpp
├── obs_test.cpp
├── obs_test.txt
├── observe.cpp
├── observe.h
├── out_comp.cpp
├── sat_code.def
├── sat_eph.c
├── sat_id.cpp
├── sat_id.txt
├── sat_id2.cpp
├── sat_id3.cpp
├── sat_util.c
├── sat_util.h
├── sdp4.cpp
├── sdp8.cpp
├── sgp.cpp
├── sgp4.cpp
├── sgp8.cpp
├── sm_sat.def
├── ssc_eph.c
├── summarize.c
├── test.tle
├── test2.cpp
├── test2.txt
├── test3.cpp
├── test_des.cpp
├── test_out.cpp
├── test_sat.cpp
├── tle2mpc.cpp
├── tle_date.c
├── tle_out.cpp
├── watcom.mak
└── zlibstub.h
Download .txt
SYMBOL INDEX (167 symbols across 40 files)

FILE: basics.cpp
  function FMod2p (line 10) | double FMod2p( const double x)
  function select_ephemeris (line 33) | int DLL_FUNC select_ephemeris( const tle_t *tle)
  function sxpx_library_version (line 64) | long DLL_FUNC sxpx_library_version( void)

FILE: common.cpp
  function sxpall_common_init (line 17) | void sxpall_common_init( const tle_t *tle, deep_arg_t *deep_arg)
  function sxpx_common_init (line 37) | void sxpx_common_init( double *params, const tle_t *tle,
  function centralize_angle (line 103) | inline double centralize_angle( const double ival)
  function sxpx_posn_vel (line 116) | int sxpx_posn_vel( const double xnode, const double a, const double ecc,

FILE: deep.cpp
  function ThetaG (line 50) | static inline double ThetaG( const double jd)
  function sxpx_set_implementation_param (line 85) | void DLL_FUNC sxpx_set_implementation_param( const int param_index,
  function sxpx_set_dpsec_integration_step (line 99) | void DLL_FUNC sxpx_set_dpsec_integration_step( const double new_step_size)
  function eval_cubic_poly (line 104) | static inline double eval_cubic_poly( const double x, const double const...
  function Deep_dpinit (line 112) | void Deep_dpinit( const tle_t *tle, deep_arg_t *deep_arg)
  function compute_dpsec_derivs (line 426) | static inline void compute_dpsec_derivs( const deep_arg_t *deep_arg,
  function Deep_dpsec (line 548) | void Deep_dpsec( const tle_t *tle, deep_arg_t *deep_arg)
  function Deep_dpper (line 626) | void Deep_dpper( const tle_t *tle, deep_arg_t *deep_arg)

FILE: dropouts.c
  function main (line 42) | int main( const int argc, const char **argv)

FILE: dynamic.cpp
  function HINSTANCE (line 23) | static HINSTANCE load_sat_code_lib( const int unload)
  function SXPX_init (line 47) | int SXPX_init( double *params, const tle_t *tle, const int sxpx_num)
  function SXPX (line 80) | int SXPX( const double tsince, const tle_t *tle, const double *params,
  function get_sat_code_lib_version (line 104) | long get_sat_code_lib_version( void)

FILE: elem2tle.cpp
  function set_tle_defaults (line 29) | static void set_tle_defaults( tle_t *tle)
  function vector_to_tle (line 39) | int vector_to_tle( tle_t *tle, const double *state_vect)
  function show_results (line 72) | static void show_results( const char *title, const tle_t *tle, const dou...
  function compute_new_state_vect (line 90) | static int compute_new_state_vect( const tle_t *tle, double *state_vect,
  function total_vector_diff (line 136) | static double total_vector_diff( const double *vect1, const double *vect2)
  function compute_simplex_point_error (line 151) | static double compute_simplex_point_error( const double *state_vect, tle...
  function try_simplex (line 171) | static double try_simplex( SIMPLEX_POINT *simp, const double factor,
  function sort_simplexes (line 190) | static void sort_simplexes( SIMPLEX_POINT *simp)
  function create_randomized_simplex (line 208) | static void create_randomized_simplex( SIMPLEX_POINT *simp, const double...
  function initialize_simplexes (line 220) | static int initialize_simplexes( SIMPLEX_POINT *simp, const double *stat...
  function find_tle_via_simplex_method (line 244) | static int find_tle_via_simplex_method( tle_t *tle, const double *state_...
  function compute_tle_from_state_vector (line 318) | int compute_tle_from_state_vector( tle_t *tle, const double *state_vect,...
  function main (line 374) | int main( const int argc, const char **argv)

FILE: fake_ast.cpp
  function main (line 16) | int main( const int argc, const char **argv)

FILE: fix_tles.cpp
  function set_checksum (line 31) | static void set_checksum( char *line)
  function usage (line 41) | static void usage( void)
  function main (line 53) | int main( const int argc, const char **argv)

FILE: get_el.cpp
  function get_angle (line 22) | static int get_angle( const char *buff)
  function sci (line 46) | static double sci( const char *string)
  function tle_checksum (line 80) | int DLL_FUNC tle_checksum( const char *buff)
  function mutant_dehex (line 109) | static inline int mutant_dehex( const char ichar)
  function get_high_value (line 144) | static double get_high_value( const char *iptr)
  function base64_to_int (line 198) | static int base64_to_int( const char c)
  function get_norad_number (line 227) | static int get_norad_number( const char *buff)
  function get_eight_places (line 259) | static inline double get_eight_places( const char *ptr)
  function parse_elements (line 278) | int DLL_FUNC parse_elements( const char *line1, const char *line2, tle_t...

FILE: get_high.cpp
  function main (line 14) | int main( const int argc, const char **argv)

FILE: get_vect.cpp
  function main (line 32) | int main( const int argc, const char **argv)

FILE: line2.cpp
  function find_tle (line 48) | int find_tle( tle_t *tle, const char *filename, const int norad_no)
  function mpc_code_to_norad_number (line 92) | static int mpc_code_to_norad_number( const char *mpc_code)
  function main (line 106) | int main( const int argc, const char **argv)

FILE: mergetle.cpp
  function load_tles_from_file (line 28) | int load_tles_from_file( FILE *ifile, TLE *tles, char *already_found)
  function FILE (line 67) | FILE *test_fopen( const char *filename, const char *permits)
  function show_tle (line 79) | void show_tle( FILE *ofile, const TLE *tle)
  function get_perigee (line 86) | static double get_perigee( const TLE *tle)
  function compare_doubles (line 95) | static int compare_doubles( const char *buff1, const char *buff2)
  function tle_compare (line 110) | int tle_compare( const TLE *tle1, const TLE *tle2, const char sort_method)
  function tle_compare_for_qsort_r (line 166) | int tle_compare_for_qsort_r( const void *a, const void *b, void *c)
  function tle_compare_for_qsort (line 173) | int tle_compare_for_qsort( const void *a, const void *b)
  function error_exit (line 180) | static void error_exit( void)
  function main (line 204) | int main( const int argc, const char **argv)

FILE: norad.h
  type tle_t (line 15) | typedef struct

FILE: norad_in.h
  type deep_arg_t (line 9) | typedef struct
  type init_t (line 49) | typedef struct

FILE: nu2vect.c
  function main (line 46) | int main( const int argc, const char **argv)

FILE: obs_tes2.cpp
  function main (line 44) | int main( const int argc, const char **argv)

FILE: obs_test.cpp
  function main (line 38) | int main( const int argc, const char **argv)

FILE: observe.cpp
  function ThetaG (line 18) | static inline double ThetaG( double jd)
  function observer_cartesian_coords (line 39) | void DLL_FUNC observer_cartesian_coords( const double jd, const double lon,
  function earth_lat_alt_to_parallax (line 50) | void DLL_FUNC earth_lat_alt_to_parallax( const double lat,
  function get_satellite_ra_dec_delta (line 61) | void DLL_FUNC get_satellite_ra_dec_delta( const double *observer_loc,
  function precess (line 83) | static void precess( const double t_centuries, double *ra, double *dec)
  function epoch_of_date_to_j2000 (line 94) | void DLL_FUNC epoch_of_date_to_j2000( const double jd, double *ra, doubl...
  function j2000_to_epoch_of_date (line 101) | void DLL_FUNC j2000_to_epoch_of_date( const double jd, double *ra, doubl...

FILE: out_comp.cpp
  function is_position_line (line 8) | static int is_position_line( const char *buff)
  function main (line 18) | int main( const int argc, const char **argv)

FILE: sat_eph.c
  type ephem_t (line 30) | typedef struct
  function show_base_60 (line 59) | static void show_base_60( char *buff, const unsigned n_millisec)
  function put_ra_in_buff (line 66) | static void put_ra_in_buff( char *buff, double ra)
  function put_dec_in_buff (line 75) | static void put_dec_in_buff( char *buff, const double dec)
  function angle_between (line 81) | static double angle_between( const double *a, const double *b)
  function desig_match (line 90) | static inline bool desig_match( const tle_t *tle, const char *desig)
  function make_orthogonal_basis (line 115) | static double make_orthogonal_basis( const double *ivect,
  function compute_angular_rates (line 136) | static double compute_angular_rates( const double *obs_pos, const double...
  function show_ephems_from (line 170) | static int show_ephems_from( const char *path_to_tles, const ephem_t *e,
  function generate_artsat_ephems (line 375) | int generate_artsat_ephems( const char *path_to_tles, const ephem_t *e)
  function set_location (line 437) | static int set_location( ephem_t *e, const char *mpc_code, const char *o...
  function fix_desig (line 517) | static void fix_desig( char *desig)
  function error_help (line 534) | static void error_help( void)
  function dummy_main (line 550) | int dummy_main( const int argc, const char **argv)
  function main (line 670) | int main( const int argc, const char **argv)
  function main (line 690) | int main( const int unused_argc, const char **unused_argv)

FILE: sat_id.cpp
  function get_mpc_data (line 180) | static int get_mpc_data( OBSERVATION *obs, const char *buff)
  function extract_csv_value (line 195) | static int extract_csv_value( char *obuff, const char *ibuff, int idx, s...
  function get_field_data (line 228) | static int get_field_data( OBSERVATION *obs, const char *buff)
  function get_station_code_data (line 275) | static int get_station_code_data( char *station_code_data,
  function offset_matches_obs (line 388) | static bool offset_matches_obs( const offset_t *offset, const OBSERVATIO...
  function set_observer_location (line 404) | static int set_observer_location( OBSERVATION *obs)
  function OBSERVATION (line 444) | static OBSERVATION *get_observations_from_file( FILE *ifile, size_t *n_f...
  function id_compare (line 570) | static int id_compare( const OBSERVATION *a, const OBSERVATION *b)
  function compare_obs (line 575) | static int compare_obs( const void *a, const void *b, void *context)
  function shellsort_r (line 589) | void shellsort_r( void *base, const size_t n_elements, const size_t elem...
  function create_orthogonal_vects (line 633) | static void create_orthogonal_vects( const double *v, double *xi_vect, d...
  function relative_motion (line 676) | double relative_motion( const double *ra_dec)
  function angular_sep (line 702) | static double angular_sep( const double delta_ra, const double dec1,
  function find_good_pair (line 731) | static double find_good_pair( OBSERVATION *obs, const size_t n_obs,
  function compute_aberration (line 767) | static void compute_aberration( const double t_cen, double *ra, double *...
  function error_exit (line 786) | static void error_exit( const int exit_code)
  function compute_artsat_ra_dec (line 809) | static int compute_artsat_ra_dec( double *ra, double *dec, double *dist,
  function is_in_range (line 852) | static bool is_in_range( const double jd, const double tle_start,
  function got_obs_in_range (line 862) | static bool got_obs_in_range( const object_t *objs, size_t n_objects,
  function _pack_intl_desig (line 879) | static int _pack_intl_desig( char *desig_out, const char *desig)
  function _compare_intl_desigs (line 916) | static int _compare_intl_desigs( const char *desig1, const char *desig2)
  function look_up_extended_identifiers (line 929) | static int look_up_extended_identifiers( const char *line0, tle_t *tle)
  function remove_redundant_desig (line 961) | static void remove_redundant_desig( char *name, const char *desig)
  function already_found_desig (line 1021) | static bool already_found_desig( const int curr_norad, size_t n_found,
  function add_tle_to_obs (line 1064) | static int add_tle_to_obs( object_t *objects, const size_t n_objects,
  function time_tag (line 1493) | static void time_tag( char *tag)
  function main (line 1535) | int main( const int argc, const char **argv)

FILE: sat_id2.cpp
  function main (line 24) | int main( const int unused_argc, const char **unused_argv)

FILE: sat_id3.cpp
  function main (line 32) | int main( const int unused_argc, const char **unused_argv)

FILE: sat_util.c
  function make_config_dir_name (line 24) | void make_config_dir_name( char *oname, const char *iname)
  function FILE (line 43) | FILE *local_then_config_fopen( const char *filename, const char *permits)
  function FILE (line 57) | FILE *local_then_config_fopen( const char *filename, const char *permits)

FILE: sdp4.cpp
  function raw_lunar_solar_position (line 35) | static void raw_lunar_solar_position( const double jd, double *lunar_xyz...
  function lunar_solar_position (line 96) | void DLL_FUNC lunar_solar_position( const double jd,
  function equatorial_to_ecliptic (line 119) | static void equatorial_to_ecliptic( double *vect)
  function ecliptic_to_equatorial (line 128) | static void ecliptic_to_equatorial( double *vect)
  function init_high_ephemeris (line 137) | static void init_high_ephemeris( double *params, const tle_t *tle)
  function SDP4_init (line 154) | void DLL_FUNC SDP4_init( double *params, const tle_t *tle)
  function vector_len (line 178) | static inline double vector_len( const double *vect)
  function calc_accel (line 187) | static int calc_accel( const double jd, const double *pos, double *accel)
  function calc_state_vector_deriv (line 219) | static int calc_state_vector_deriv( const double jd,
  function high_ephemeris (line 237) | static int high_ephemeris( double tsince, const tle_t *tle, const double...
  function SDP4 (line 300) | int DLL_FUNC SDP4( const double tsince, const tle_t *tle, const double *...

FILE: sdp8.cpp
  function SDP8_init (line 21) | void DLL_FUNC SDP8_init( double *params, const tle_t *tle)
  function SDP8 (line 76) | int DLL_FUNC SDP8( const double tsince, const tle_t *tle, const double *...

FILE: sgp.cpp
  function SGP_init (line 19) | void DLL_FUNC SGP_init( double *params, const tle_t *tle)
  function SGP (line 49) | int DLL_FUNC SGP( const double tsince, const tle_t *tle, const double *p...

FILE: sgp4.cpp
  function SGP4_init (line 35) | void DLL_FUNC SGP4_init( double *params, const tle_t *tle)
  function SGP4 (line 89) | int DLL_FUNC SGP4( const double tsince, const tle_t *tle, const double *...

FILE: sgp8.cpp
  function sxp8_common_init (line 35) | void sxp8_common_init( double *params, const tle_t *tle, deep_arg_t *dee...
  function SGP8_init (line 65) | void DLL_FUNC SGP8_init( double *params, const tle_t *tle)
  function SGP8 (line 212) | int DLL_FUNC SGP8( const double tsince, const tle_t *tle, const double *...

FILE: ssc_eph.c
  function dump_to_file (line 35) | static void dump_to_file( const int mms_no, const double t0, const doubl...
  function main (line 69) | int main( const int argc, const char **argv)

FILE: summarize.c
  function get_range_info (line 31) | static void get_range_info( const char *filename,
  function main (line 83) | int main( const int unused_argc, const char **unused_argv)

FILE: test2.cpp
  function main (line 20) | int main( int argc, char **argv)

FILE: test3.cpp
  function main (line 28) | int main( int argc, char **argv)

FILE: test_des.cpp
  function main (line 23) | int main( const int argc, const char **argv)

FILE: test_out.cpp
  function greg_day_to_dmy (line 12) | void greg_day_to_dmy( const long jd, int *day,
  function main (line 71) | int main( const int argc, const char **argv)

FILE: test_sat.cpp
  function main (line 85) | int main( const int argc, const char **unused_argv)

FILE: tle2mpc.cpp
  function error_exit (line 19) | static void error_exit( void)
  function main (line 34) | int main( const int argc, const char **argv)

FILE: tle_date.c
  function extract_tle_for_date (line 19) | static void extract_tle_for_date( const char *fname, const double mjd)
  function err_exit (line 87) | static void err_exit( void)
  function main (line 96) | int main( const int argc, const char **argv)
  function main (line 121) | int main( void)

FILE: tle_out.cpp
  function add_tle_checksum_data (line 25) | static int add_tle_checksum_data( char *buff)
  function zero_to_two_pi (line 50) | static double zero_to_two_pi( double angle_in_radians)
  function set_high_value (line 61) | static void set_high_value( char *obuff, const double value)
  function put_sci (line 83) | static void put_sci( char *obuff, double ival)
  function int_to_base64 (line 137) | static char int_to_base64( const int digit)
  function store_norad_number_in_alpha5 (line 155) | static void store_norad_number_in_alpha5( char *obuff, const int norad_n...
  function write_elements_in_tle_format (line 207) | void DLL_FUNC write_elements_in_tle_format( char *buff, const tle_t *tle)
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (476K chars).
[
  {
    "path": ".github/workflows/github_actions_build.yml",
    "chars": 1256,
    "preview": "# Shameless copy of @AlastairUK's fine work for the jpl_eph repo.\n# Aside from the comment,  this is almost a byte-for-b"
  },
  {
    "path": ".gitignore",
    "chars": 341,
    "preview": "# 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*.dyl"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2020,  Project Pluto\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "README.md",
    "chars": 951,
    "preview": "# sat_code\n\nC/C++ code for the SGP4/SDP4 satellite motion model,  and for manipulating TLEs\n(Two-Line Elements). Full de"
  },
  {
    "path": "basics.cpp",
    "chars": 2076,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n/*-"
  },
  {
    "path": "common.cpp",
    "chars": 9416,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE. */\n\n#include <math.h>\n#include <assert.h>\n#include \"norad.h\"\n#includ"
  },
  {
    "path": "deep.cpp",
    "chars": 32705,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE. */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n    "
  },
  {
    "path": "dropouts.c",
    "chars": 5275,
    "preview": "/* Code to check for the existence of certain artsats in Space-Track's\nmaster TLE list.  Occasionally,  they've dropped "
  },
  {
    "path": "dynamic.cpp",
    "chars": 3426,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/* Hooks to allow the satellite code to be accessed from a DLL,"
  },
  {
    "path": "elem2tle.cpp",
    "chars": 17793,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/* MOSTLY OBSOLETE.  See 'eph2tle.cpp' in the Find_Orb project\n"
  },
  {
    "path": "fake_ast.cpp",
    "chars": 3741,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\n   This program will generate simulated geocentric observations\nfor"
  },
  {
    "path": "fix_tles.cpp",
    "chars": 3729,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\nfix_tles : change TLE data and replace with correct checksums\n\n   I"
  },
  {
    "path": "get_el.cpp",
    "chars": 11703,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <string.h>\n#include <stdint.h>\n#include <assert.h>\n#in"
  },
  {
    "path": "get_high.cpp",
    "chars": 1339,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/* Code to extract elements for high-flying artsats. Give */\n/*"
  },
  {
    "path": "get_vect.cpp",
    "chars": 4843,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\n   This code can read a TLE and compute a geocentric state vector,\n"
  },
  {
    "path": "line2.cpp",
    "chars": 6073,
    "preview": "/* See LICENSE.   NOTE that this has been obsoleted by the 'add_off.c'\nprogram in the 'lunar' repository (q.v.).  The on"
  },
  {
    "path": "makefile",
    "chars": 6676,
    "preview": "# 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 a"
  },
  {
    "path": "mergetle.cpp",
    "chars": 8517,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\nCode to read one or more files of TLEs,  remove duplicates,\nand sor"
  },
  {
    "path": "msvc.mak",
    "chars": 2106,
    "preview": "# Makefile for MSVC\nall:  dropouts.exe fix_tles.exe line2.exe mergetle.exe obs_test.exe \\\n   obs_tes2.exe out_comp.exe s"
  },
  {
    "path": "msvc_dll.mak",
    "chars": 1934,
    "preview": "# MSVC makefile for a DLL version\n# NOTE: hasn't been used or updated in years;  some work required\n# before it would be"
  },
  {
    "path": "norad.h",
    "chars": 5569,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  norad.h v. 01.beta 03/17/2001\n *\n *  Header file for nor"
  },
  {
    "path": "norad_in.h",
    "chars": 3030,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#ifndef NORAD_IN_H\n#define NORAD_IN_H\n\n/* Common \"internal\" arg"
  },
  {
    "path": "nu2vect.c",
    "chars": 3619,
    "preview": "/* 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 "
  },
  {
    "path": "nu_readme.txt",
    "chars": 1753,
    "preview": "(Translation/additions to ftp://ftp.kiam1.rssi.ru/pub/gps/spectr-rg/nu/00_read.me .\nThis also applies to the files for S"
  },
  {
    "path": "obs_tes2.cpp",
    "chars": 6879,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n    obs_tes2.cpp     12 December 2002\n\n   (Revised slightly "
  },
  {
    "path": "obs_test.cpp",
    "chars": 5709,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n    obs_test.cpp     23 September 2002\n\n   (Revised slightly"
  },
  {
    "path": "obs_test.txt",
    "chars": 915,
    "preview": "#  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"
  },
  {
    "path": "observe.cpp",
    "chars": 3651,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"observe.h\"\n\n/*  Assorted functions "
  },
  {
    "path": "observe.h",
    "chars": 962,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#ifdef _WIN32\n#define DLL_FUNC __stdcall\n#else\n#define DLL_FUNC"
  },
  {
    "path": "out_comp.cpp",
    "chars": 1669,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#inc"
  },
  {
    "path": "sat_code.def",
    "chars": 858,
    "preview": "LIBRARY sat_code\r\nEXPORTS\r\n   SGP_init                          @1\r\n   SGP4_init                         @2\r\n   SGP8_ini"
  },
  {
    "path": "sat_eph.c",
    "chars": 27805,
    "preview": "#include <stdio.h>\n#include <ctype.h>\n#include <string.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <assert.h>\n#"
  },
  {
    "path": "sat_id.cpp",
    "chars": 71342,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n    sat_id.cpp     8 March 2003,  with updates as listed bel"
  },
  {
    "path": "sat_id.txt",
    "chars": 6571,
    "preview": "-a(date) : only consider observations after (date)\n-b(date) : only consider observations before (date)\n-c       : check "
  },
  {
    "path": "sat_id2.cpp",
    "chars": 4651,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#incl"
  },
  {
    "path": "sat_id3.cpp",
    "chars": 4013,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#incl"
  },
  {
    "path": "sat_util.c",
    "chars": 1264,
    "preview": "#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#include \"sat_util.h\"\n\nchar *fgets_trimmed( char *buff, const"
  },
  {
    "path": "sat_util.h",
    "chars": 531,
    "preview": "#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, "
  },
  {
    "path": "sdp4.cpp",
    "chars": 12925,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stddef.h>\n#include <math.h>\n#include \"norad.h\"\n#inclu"
  },
  {
    "path": "sdp8.cpp",
    "chars": 6400,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#de"
  },
  {
    "path": "sgp.cpp",
    "chars": 4748,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include \"norad.h\"\n#include \"norad_in.h\"\n\n#de"
  },
  {
    "path": "sgp4.cpp",
    "chars": 4473,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include <stdio.h>\n#include \"norad.h\"\n#includ"
  },
  {
    "path": "sgp8.cpp",
    "chars": 11526,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <math.h>\n#include <string.h>\n#include \"norad.h\"\n#inclu"
  },
  {
    "path": "sm_sat.def",
    "chars": 210,
    "preview": "LIBRARY sm_sat\r\nEXPORTS\r\n   SGP4_init                    @2\r\n   SGP4                         @7\r\n   select_ephemeris    "
  },
  {
    "path": "ssc_eph.c",
    "chars": 4065,
    "preview": "/* Code to convert ephems from SSCWeb into the format 'eph2tle'\nuses to generate TLEs.  The usefulness here is that you "
  },
  {
    "path": "summarize.c",
    "chars": 3282,
    "preview": "/* Code to read 'tle_list.txt' and insert # Range: lines for\nfiles that lack them,  and (for files containing only one\no"
  },
  {
    "path": "test.tle",
    "chars": 10610,
    "preview": "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."
  },
  {
    "path": "test2.cpp",
    "chars": 6827,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  test2.cpp      5 July 2002\n *\n *  A skeleton main() func"
  },
  {
    "path": "test2.txt",
    "chars": 76607,
    "preview": "1 11801U          80230.29629788  .01431103  00000-0  14311-1       2\n2 11801U 46.7916 230.4354 7318036  47.4722  10.411"
  },
  {
    "path": "test3.cpp",
    "chars": 4467,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  test3.cpp      8 October 2002\n *\n *  A skeleton main() f"
  },
  {
    "path": "test_des.cpp",
    "chars": 2119,
    "preview": "/* Copyright (C) 2021, Project Pluto.  See LICENSE.  */\n/* Code to test Alpha-5 and 'extended' Alpha-5 (Super-5) designa"
  },
  {
    "path": "test_out.cpp",
    "chars": 5892,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <string.h>\n#include <math.h>\n#inclu"
  },
  {
    "path": "test_sat.cpp",
    "chars": 9727,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n/*\n *  main.c          April 10  2001\n *\n *  A skeleton main() "
  },
  {
    "path": "tle2mpc.cpp",
    "chars": 3961,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n#inc"
  },
  {
    "path": "tle_date.c",
    "chars": 4666,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.  */\n\n#include <stdio.h>\n#include <time.h>\n#include <stdlib.h>\n#inclu"
  },
  {
    "path": "tle_out.cpp",
    "chars": 9037,
    "preview": "/* Copyright (C) 2018, Project Pluto.  See LICENSE.\n\n   tle_out.cpp: code to convert the in-memory artificial satellite\n"
  },
  {
    "path": "watcom.mak",
    "chars": 1615,
    "preview": "# 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\no"
  },
  {
    "path": "zlibstub.h",
    "chars": 138,
    "preview": "#define gzFile FILE *\n#define gzopen fopen\n#define gzgets( ifile, buff, buffsize)    fgets( buff, buffsize, ifile)\n#defi"
  }
]

About this extraction

This page contains the full source code of the Bill-Gray/sat_code GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (448.3 KB), approximately 149.9k tokens, and a symbol index with 167 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!