1 #define PY_SSIZE_T_CLEAN
2 #include <Python.h>
3 #include "SGP4.h"
4 #include "structmember.h"
5 
6 /* Whether scanf() prefers commas as the decimal point: true means that
7    we cannot count on the current locale to interpret numbers correctly
8    when the Vallado C++ code calls `sscanf()`. */
9 
10 static bool switch_locale;
11 
12 /* Satrec object that wraps a single raw SGP4 struct, and a Satrec array
13    that can broadcast into NumPy arrays. */
14 
15 typedef struct {
16     PyObject_HEAD
17     elsetrec satrec;
18 } SatrecObject;
19 
20 typedef struct {
21     PyObject_VAR_HEAD
22     elsetrec satrec[0];
23 } SatrecArrayObject;
24 
25 /* Support routine that is used to support NumPy array broadcasting for
26    both individual satellite objects and also arrays. */
27 
28 static PyObject *
_vectorized_sgp4(PyObject * args,elsetrec * raw_satrec_array,int imax)29 _vectorized_sgp4(PyObject *args, elsetrec *raw_satrec_array, int imax)
30 {
31     PyObject *jd_arg, *fr_arg, *e_arg, *r_arg, *v_arg;
32     Py_buffer jd_buf, fr_buf, e_buf, r_buf, v_buf;
33     PyObject *rv = NULL;
34 
35     // To prepare for "cleanup:" below.
36     jd_buf.buf = fr_buf.buf = e_buf.buf = r_buf.buf = v_buf.buf = NULL;
37 
38     if (!PyArg_ParseTuple(args, "OOOOO:sgp4",
39                           &jd_arg, &fr_arg, &e_arg, &r_arg, &v_arg))
40         return NULL;
41 
42     if (PyObject_GetBuffer(jd_arg, &jd_buf, PyBUF_SIMPLE)) goto cleanup;
43     if (PyObject_GetBuffer(fr_arg, &fr_buf, PyBUF_SIMPLE)) goto cleanup;
44     if (PyObject_GetBuffer(e_arg, &e_buf, PyBUF_WRITABLE)) goto cleanup;
45     if (PyObject_GetBuffer(r_arg, &r_buf, PyBUF_WRITABLE)) goto cleanup;
46     if (PyObject_GetBuffer(v_arg, &v_buf, PyBUF_WRITABLE)) goto cleanup;
47 
48     if (jd_buf.len != fr_buf.len) {
49         PyErr_SetString(PyExc_ValueError, "jd and fr must have the same shape");
50         goto cleanup;
51     }
52 
53     // This extra block allows the "goto" statements above to jump
54     // across these further variable declarations.
55     {
56         Py_ssize_t jmax = jd_buf.len / sizeof(double);
57 
58         if ((r_buf.len != (Py_ssize_t) sizeof(double) * imax * jmax * 3) ||
59             (v_buf.len != (Py_ssize_t) sizeof(double) * imax * jmax * 3) ||
60             (e_buf.len != (Py_ssize_t) sizeof(uint8_t) * imax * jmax)) {
61             PyErr_SetString(PyExc_ValueError, "bad output array dimension");
62             goto cleanup;
63         }
64 
65         double *jd = (double*) jd_buf.buf;
66         double *fr = (double*) fr_buf.buf;
67         double *r = (double*) r_buf.buf;
68         double *v = (double*) v_buf.buf;
69         uint8_t *e = (uint8_t*) e_buf.buf;
70 
71 #pragma omp parallel for
72         for (Py_ssize_t i=0; i < imax; i++) {
73             elsetrec &satrec = raw_satrec_array[i];
74             for (Py_ssize_t j=0; j < jmax; j++) {
75                 double t = (jd[j] - satrec.jdsatepoch) * 1440.0
76                          + (fr[j] - satrec.jdsatepochF) * 1440.0;
77                 Py_ssize_t k1 = i * jmax + j;
78                 Py_ssize_t k3 = 3 * k1;
79                 SGP4Funcs::sgp4(satrec, t, r + k3, v + k3);
80                 e[k1] = (uint8_t) satrec.error;
81                 if (satrec.error && satrec.error < 6) {
82                     r[k3] = r[k3+1] = r[k3+2] = v[k3] = v[k3+1] = v[k3+2] = NAN;
83                 }
84             }
85         }
86     }
87 
88     Py_INCREF(Py_None);
89     rv = Py_None;
90  cleanup:
91     if (jd_buf.buf) PyBuffer_Release(&jd_buf);
92     if (fr_buf.buf) PyBuffer_Release(&fr_buf);
93     if (r_buf.buf) PyBuffer_Release(&r_buf);
94     if (v_buf.buf) PyBuffer_Release(&v_buf);
95     if (e_buf.buf) PyBuffer_Release(&e_buf);
96     return rv;
97 }
98 
99 /* Details of the "Satrec" satellite object. */
100 
101 static PyObject *
Satrec_twoline2rv(PyTypeObject * cls,PyObject * args)102 Satrec_twoline2rv(PyTypeObject *cls, PyObject *args)
103 {
104     char *string1, *string2, line1[130], line2[130];
105     gravconsttype whichconst = wgs72;
106     double dummy;
107 
108     if (!PyArg_ParseTuple(args,
109             "ss|i:twoline2rv",
110             &string1, &string2, &whichconst))
111         return NULL;
112 
113     // Copy both lines, since twoline2rv() might write to both buffers.
114     strncpy(line1, string1, 130);
115     strncpy(line2, string2, 130);
116 
117     // Truncate the line before any checksum characters, if present;
118     // otherwise the C++ scanf() interprets each checksum character as
119     // part of the preceding value.
120     line1[68] = '\0';
121     line2[68] = '\0';
122 
123     /* Allocate the new object. */
124     SatrecObject *self = (SatrecObject*) cls->tp_alloc(cls, 0);
125     if (!self)
126         return NULL;
127 
128     /* Correct for locales that use a comma as the decimal point, since
129        users report that the scanf() function on macOS is sensitive to
130        locale when parsing floats.  This operation is not thread-safe,
131        but we have not released the GIL. */
132     float f;
133     sscanf("1,5", "%f", &f);
134     switch_locale = (f == 1.5);
135 
136     char *old_locale = NULL;
137     if (switch_locale)
138         old_locale = setlocale(LC_NUMERIC, "C");
139 
140     /* Leading spaces in a catalog number make scanf() in the official
141        code consume the Classification letter as part of the catalog
142        number.  (The first character of the International Designator
143        then gets consumed as the Classification instead.)  But no
144        parsing error is reported, which is bad for users, so let's avoid
145        the situation by adding leading zeros ourselves. */
146     for (int i=2; i<7; i++) {
147         if (line1[i] == ' ') line1[i] = '0';
148         if (line2[i] == ' ') line2[i] = '0';
149     }
150 
151     /* Call the official routine. */
152     SGP4Funcs::twoline2rv(line1, line2, ' ', ' ', 'i', whichconst,
153                           dummy, dummy, dummy, self->satrec);
154 
155     /* Usability bonus: round the fractional day to exactly the eight
156        digits specified in the TLE. */
157     self->satrec.jdsatepochF = round(self->satrec.jdsatepochF * 1e8) / 1e8;
158 
159     /* To avoid having scanf() interpret the "intldesg" as zero or as
160        several fields, the C++ library changes spaces to periods and
161        underscores.  Let's convert them back to avoid surprising users
162        and to match our Python implementation. */
163     if (self->satrec.intldesg[0] == '.')
164          self->satrec.intldesg[0] = ' ';
165     for (int i=1; i<11; i++)
166          if (self->satrec.intldesg[i] == '_')
167               self->satrec.intldesg[i] = ' ';
168 
169     /* Restore previous locale. */
170     if (switch_locale)
171         setlocale(LC_NUMERIC, old_locale);
172 
173     return (PyObject*) self;
174 }
175 
176 static PyObject *
Satrec_sgp4init(PyObject * self,PyObject * args)177 Satrec_sgp4init(PyObject *self, PyObject *args)
178 {
179     char satnum_str[6];
180     int whichconst;  /* "int" rather than "gravconsttype" so we know size */
181     int opsmode;     /* "int" rather than "char" because "C" needs an int */
182     long int satnum;
183     double epoch, bstar, ndot, nddot;
184     double ecco, argpo, inclo, mo, no_kozai, nodeo;
185 
186     if (!PyArg_ParseTuple(args, "iCldddddddddd:sgp4init", &whichconst,
187                           &opsmode, &satnum, &epoch, &bstar, &ndot, &nddot,
188                           &ecco, &argpo, &inclo, &mo, &no_kozai, &nodeo))
189         return NULL;
190 
191     // See https://www.space-track.org/documentation#tle-alpha5
192     if (satnum < 100000) {
193         snprintf(satnum_str, 6, "%ld", satnum);
194     } else {
195         char c = 'A' + satnum / 10000 - 10;
196         if (c > 'I') c++;
197         if (c > 'O') c++;
198         satnum_str[0] = c;
199         snprintf(satnum_str + 1, 5, "%04ld", satnum % 10000);
200     }
201 
202     elsetrec &satrec = ((SatrecObject*) self)->satrec;
203 
204     SGP4Funcs::sgp4init((gravconsttype) whichconst, opsmode, satnum_str, epoch,
205                         bstar, ndot, nddot, ecco, argpo, inclo, mo, no_kozai,
206                         nodeo, satrec);
207 
208     /* Populate date fields that SGP4Funcs::twoline2rv would set. */
209     double whole;
210     double fraction = modf(epoch, &whole);
211     double whole_jd = whole + 2433281.5;
212 
213     /* Go out on a limb: if `epoch` has no decimal digits past the 8
214        decimal places stored in a TLE, then assume the user is trying
215        to specify an exact decimal fraction. */
216     double epoch8 = epoch * 1e8;
217     if (round(epoch8) == epoch8) {
218         fraction = round(fraction * 1e8) / 1e8;
219     }
220 
221     satrec.jdsatepoch = whole_jd;
222     satrec.jdsatepochF = fraction;
223 
224     int y, m, d, H, M;
225     double S, jan0jd, jan0fr /* always comes out 0.0 */;
226     SGP4Funcs::invjday_SGP4(2433281.5, whole, y, m, d, H, M, S);
227     SGP4Funcs::jday_SGP4(y, 1, 0, 0, 0, 0.0, jan0jd, jan0fr);
228     satrec.epochyr = y % 100;
229     satrec.epochdays = whole_jd - jan0jd + fraction;
230 
231     /* Return true as sgp4init does, satrec.error contains any error codes */
232 
233     Py_INCREF(Py_None);
234     return Py_None;
235 }
236 
237 static PyObject *
Satrec_sgp4_tsince(PyObject * self,PyObject * args)238 Satrec_sgp4_tsince(PyObject *self, PyObject *args)
239 {
240     double tsince, r[3], v[3];
241 
242     if (!PyArg_ParseTuple(args, "d:sgp4_tsince", &tsince))
243         return NULL;
244     elsetrec &satrec = ((SatrecObject*) self)->satrec;
245     SGP4Funcs::sgp4(satrec, tsince, r, v);
246     if (satrec.error && satrec.error < 6)
247         r[0] = r[1] = r[2] = v[0] = v[1] = v[2] = NAN;
248     return Py_BuildValue("i(fff)(fff)", satrec.error,
249                          r[0], r[1], r[2], v[0], v[1], v[2]);
250 }
251 
252 static PyObject *
Satrec_sgp4(PyObject * self,PyObject * args)253 Satrec_sgp4(PyObject *self, PyObject *args)
254 {
255     double jd, fr, r[3], v[3];
256     if (!PyArg_ParseTuple(args, "dd:sgp4", &jd, &fr))
257         return NULL;
258     elsetrec &satrec = ((SatrecObject*) self)->satrec;
259     double tsince = (jd - satrec.jdsatepoch) * 1440.0
260                   + (fr - satrec.jdsatepochF) * 1440.0;
261     SGP4Funcs::sgp4(satrec, tsince, r, v);
262     if (satrec.error && satrec.error < 6)
263         r[0] = r[1] = r[2] = v[0] = v[1] = v[2] = NAN;
264     return Py_BuildValue("i(fff)(fff)", satrec.error,
265                          r[0], r[1], r[2], v[0], v[1], v[2]);
266 }
267 
268 static PyObject *
Satrec__sgp4(PyObject * self,PyObject * args)269 Satrec__sgp4(PyObject *self, PyObject *args)
270 {
271     SatrecObject *obj = (SatrecObject*) self;
272     elsetrec *raw_satrec_array = &(obj->satrec);
273     Py_ssize_t imax = 1;
274     return _vectorized_sgp4(args, raw_satrec_array, imax);
275 }
276 
277 static PyMethodDef Satrec_methods[] = {
278     {"twoline2rv", (PyCFunction)Satrec_twoline2rv, METH_VARARGS | METH_CLASS,
279      PyDoc_STR("Initialize the record from two lines of TLE text and an optional gravity constant.")},
280     {"sgp4init", (PyCFunction)Satrec_sgp4init, METH_VARARGS,
281      PyDoc_STR("Initialize the record from orbital elements.")},
282     {"sgp4", (PyCFunction)Satrec_sgp4, METH_VARARGS,
283      PyDoc_STR("Given a modified julian date, return position and velocity.")},
284     {"_sgp4", (PyCFunction)Satrec__sgp4, METH_VARARGS,
285      PyDoc_STR("Given an array of modified julian dates, return position and velocity arrays.")},
286     {"sgp4_tsince", (PyCFunction)Satrec_sgp4_tsince, METH_VARARGS,
287      PyDoc_STR("Given minutes since epoch, return position and velocity.")},
288     {NULL, NULL}
289 };
290 
291 #define O(member) offsetof(SatrecObject, satrec.member)
292 
293 static PyMemberDef Satrec_members[] = {
294     /* Listed in the order they appear in a TLE record. */
295 
296     {"operationmode", T_CHAR, O(operationmode), READONLY,
297      PyDoc_STR("Operation mode: 'a' legacy AFSPC, or 'i' improved.")},
298     {"jdsatepoch", T_DOUBLE, O(jdsatepoch), 0,
299      PyDoc_STR("Julian date of epoch, day number (see jdsatepochF).")},
300     {"jdsatepochF", T_DOUBLE, O(jdsatepochF), 0,
301      PyDoc_STR("Julian date of epoch, fraction of day (see jdsatepoch).")},
302     {"classification", T_CHAR, O(classification), 0,
303      "Usually U=Unclassified, C=Classified, or S=Secret."},
304     /* intldesg: inline character array; see Satrec_getset. */
305     {"epochyr", T_INT, O(epochyr), 0,
306      PyDoc_STR("Year of this element set's epoch (see epochdays). Not set by sgp4init().")},
307     {"epochdays", T_DOUBLE, O(epochdays), 0,
308      PyDoc_STR("Day of the year of this element set's epoch (see epochyr). Not set by sgp4init().")},
309     {"ndot", T_DOUBLE, O(ndot), READONLY,
310      PyDoc_STR("Ballistic Coefficient in revs/day.")},
311     {"nddot", T_DOUBLE, O(nddot), READONLY,
312      PyDoc_STR("Second Derivative of Mean Motion in revs/day^3.")},
313     {"bstar", T_DOUBLE, O(bstar), READONLY,
314      PyDoc_STR("Drag Term in inverse Earth radii.")},
315     {"ephtype", T_INT, O(ephtype), 0,
316      PyDoc_STR("Ephemeris type (should be 0 in published TLEs).")},
317     {"elnum", T_LONG, O(elnum), 0,
318      PyDoc_STR("Element set number.")},
319     {"inclo", T_DOUBLE, O(inclo), READONLY,
320      PyDoc_STR("Inclination in radians.")},
321     {"nodeo", T_DOUBLE, O(nodeo), READONLY,
322      PyDoc_STR("Right ascension of ascending node in radians.")},
323     {"ecco", T_DOUBLE, O(ecco), READONLY,
324      PyDoc_STR("Eccentricity.")},
325     {"argpo", T_DOUBLE, O(argpo), READONLY,
326      PyDoc_STR("Argument of perigee in radians.")},
327     {"mo", T_DOUBLE, O(mo), READONLY,
328      PyDoc_STR("Mean anomaly in radians.")},
329     {"no_kozai", T_DOUBLE, O(no_kozai), READONLY,
330      PyDoc_STR("Mean motion in radians per minute.")},
331     {"revnum", T_LONG, O(revnum), 0,
332      PyDoc_STR("Integer revolution number at the epoch.")},
333 
334     /* For compatibility with the old struct members, also accept the
335        plain name "no". */
336 
337     {"no", T_DOUBLE, O(no_kozai), READONLY,
338      PyDoc_STR("Alias for the more carefully named ``no_kozai``.")},
339 
340     /* Derived values that do not appear explicitly in the TLE. */
341 
342     {"method", T_CHAR, O(method), READONLY,
343      PyDoc_STR("Method, either 'n' near earth or 'd' deep space.")},
344     {"error", T_INT, O(method), READONLY,
345      PyDoc_STR("Error code (1-6) documented in sgp4()")},
346     {"a", T_DOUBLE, O(a), READONLY,
347      PyDoc_STR("semi-major axis")},
348     {"altp", T_DOUBLE, O(altp), READONLY,
349      PyDoc_STR("altitude of perigee")},
350     {"alta", T_DOUBLE, O(alta), READONLY,
351      PyDoc_STR("altitude of perigee")},
352 
353     /* Single averaged mean elements */
354 
355     {"am", T_DOUBLE, O(am), READONLY,
356      PyDoc_STR("am: Average semi-major axis")},
357     {"em", T_DOUBLE, O(em), READONLY,
358      PyDoc_STR("em: Average eccentricity")},
359     {"im", T_DOUBLE, O(im), READONLY,
360      PyDoc_STR("im: Average inclination")},
361     {"Om", T_DOUBLE, O(Om), READONLY,
362      PyDoc_STR("Om: Average right ascension of ascending node")},
363     {"om", T_DOUBLE, O(om), READONLY,
364      PyDoc_STR("om: Average argument of perigee")},
365     {"mm", T_DOUBLE, O(mm), READONLY,
366      PyDoc_STR("mm: Average mean anomaly")},
367     {"nm", T_DOUBLE, O(nm), READONLY,
368      PyDoc_STR("nm: Average mean motion")},
369 
370     /* Gravity-constant dependent values (initialized by sgp4init() */
371 
372     {"tumin", T_DOUBLE, O(tumin), READONLY,
373      PyDoc_STR("minutes in one time unit")},
374     {"mu", T_DOUBLE, O(mus), READONLY,
375      PyDoc_STR("Earth gravitational parameter")},
376     {"radiusearthkm", T_DOUBLE, O(radiusearthkm), READONLY,
377      PyDoc_STR("radius of the earth in km")},
378     {"xke", T_DOUBLE, O(xke), READONLY,
379      PyDoc_STR("reciprocal of tumin")},
380     {"j2", T_DOUBLE, O(j2), READONLY,
381      PyDoc_STR("un-normalized zonal harmonic j2 value")},
382     {"j3", T_DOUBLE, O(j3), READONLY,
383      PyDoc_STR("un-normalized zonal harmonic j3 value")},
384     {"j4", T_DOUBLE, O(j4), READONLY,
385      PyDoc_STR("un-normalized zonal harmonic j4 value")},
386     {"j3oj2", T_DOUBLE, O(j3oj2), READONLY,
387      PyDoc_STR("j3 divided by j2")},
388 
389     /* Other convenience variables (some required by propagation.py) */
390 
391     {"t", T_DOUBLE, O(t), READONLY,
392      PyDoc_STR("Last tsince input to sgp4()")},
393     {"mdot", T_DOUBLE, O(mdot), READONLY,
394      PyDoc_STR("mean anomaly dot (rate)")},
395     {"argpdot", T_DOUBLE, O(argpdot), READONLY,
396      PyDoc_STR("argument of perigee dot (rate)")},
397     {"nodedot", T_DOUBLE, O(nodedot), READONLY,
398      PyDoc_STR("right ascension of ascending node dot (rate)")},
399     {"gsto", T_DOUBLE, O(gsto), READONLY,
400      PyDoc_STR("gsto: greenwich sidereal time")},
401 
402     {NULL}
403 };
404 
405 #undef O
406 
407 static PyObject *
get_intldesg(SatrecObject * self,void * closure)408 get_intldesg(SatrecObject *self, void *closure)
409 {
410     int length = 0;
411     char *s = self->satrec.intldesg;
412     while (length <= 7 && s[length])
413         length++;
414     while (length && s[length-1] == ' ')
415         length--;
416     return PyUnicode_FromStringAndSize(s, length);
417 }
418 
419 static int
set_intldesg(SatrecObject * self,PyObject * value,void * closure)420 set_intldesg(SatrecObject *self, PyObject *value, void *closure)
421 {
422     if (!PyUnicode_Check(value))
423         return -1;
424     const char *s = PyUnicode_AsUTF8(value);
425     if (!s)
426         return -1;
427     strncpy(self->satrec.intldesg, s, 11);
428     return 0;
429 }
430 
431 static PyObject *
get_satnum(SatrecObject * self,void * closure)432 get_satnum(SatrecObject *self, void *closure)
433 {
434     char *s = self->satrec.satnum;
435     long n;
436     if (strlen(s) < 5 || s[0] <= '9')
437         n = atol(s);
438     else if (s[0] <= 'I')
439         n = (s[0] - 'A' + 10) * 10000 + atol(s + 1);
440     else if (s[0] <= 'O')
441         n = (s[0] - 'A' + 9) * 10000 + atol(s + 1);
442     else
443         n = (s[0] - 'A' + 8) * 10000 + atol(s + 1);
444     return PyLong_FromLong(n);
445 }
446 
447 static PyGetSetDef Satrec_getset[] = {
448     {"intldesg", (getter)get_intldesg, (setter)set_intldesg,
449      PyDoc_STR("International Designator: a string of up to 7 characters"
450                " from the first line of the TLE that typically provides"
451                " two digits for the launch year, a 3-digit launch number,"
452                " and one or two letters for which piece of the launch.")},
453     {"satnum", (getter)get_satnum, NULL,
454      PyDoc_STR("Satellite number, from characters 3-7 of each TLE line.")},
455     {NULL},
456 };
457 
458 static PyTypeObject SatrecType = {
459     PyVarObject_HEAD_INIT(NULL, 0)
460     /* See the module initialization function at the bottom of this file. */
461 };
462 
463 /* Details of the SatrecArray. */
464 
465 static Py_ssize_t
Satrec_len(PyObject * self)466 Satrec_len(PyObject *self) {
467     return ((SatrecArrayObject*)self)->ob_base.ob_size;
468 }
469 
470 static PySequenceMethods SatrecArray_as_sequence = {
471     Satrec_len
472 };
473 
474 static PyObject *
SatrecArray_new(PyTypeObject * type,PyObject * args,PyObject * kwds)475 SatrecArray_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
476 {
477     PyObject *sequence;
478     if (!PyArg_ParseTuple(args, "O:SatrecArray", &sequence))
479         return NULL;
480 
481     Py_ssize_t length = PySequence_Length(sequence);
482     if (length == -1)
483         return NULL;
484 
485     return type->tp_alloc(type, length);
486 }
487 
488 
489 static int
SatrecArray_init(SatrecArrayObject * self,PyObject * args,PyObject * kwds)490 SatrecArray_init(SatrecArrayObject *self, PyObject *args, PyObject *kwds)
491 {
492     PyObject *sequence;
493     if (!PyArg_ParseTuple(args, "O:SatrecArray", &sequence))
494         return -1;
495 
496     Py_ssize_t length = PySequence_Length(sequence);
497     if (length == -1)
498         return -1;
499 
500     for (Py_ssize_t i=0; i<length; i++) {
501         PyObject *item = PySequence_GetItem(sequence, i);
502         if (!item)
503             return -1;
504         if (!PyObject_IsInstance(item, (PyObject*) &SatrecType)) {
505             PyErr_Format(PyExc_ValueError, "every item must be a Satrec,"
506                          " but element %d is: %R", i, item);
507             Py_DECREF(item);
508             return -1;
509         }
510         self->satrec[i] = ((SatrecObject *)item)->satrec;
511         Py_DECREF(item);
512     }
513     return 0;
514 }
515 
516 static PyObject *
SatrecArray_sgp4(PyObject * self,PyObject * args)517 SatrecArray_sgp4(PyObject *self, PyObject *args)
518 {
519     SatrecArrayObject *satrec_array = (SatrecArrayObject*) self;
520     elsetrec *raw_satrec_array = &(satrec_array->satrec[0]);
521     Py_ssize_t imax = satrec_array->ob_base.ob_size;
522     return _vectorized_sgp4(args, raw_satrec_array, imax);
523 }
524 
525 static PyMethodDef SatrecArray_methods[] = {
526     {"_sgp4", (PyCFunction)SatrecArray_sgp4, METH_VARARGS,
527      PyDoc_STR("Given array of minutes since epoch, write"
528                " positions, velocities, and errors to arrays.")},
529     {NULL, NULL}
530 };
531 
532 static PyTypeObject SatrecArrayType = {
533     PyVarObject_HEAD_INIT(NULL, sizeof(elsetrec))
534     /* See the module initialization function at the bottom of this file. */
535 };
536 
537 /* The module that ties it all together. */
538 static PyModuleDef module = {
539     PyModuleDef_HEAD_INIT,
540     "sgp4.vallado_cpp",
541     "Official C++ SGP4 implementation.",
542     -1
543 };
544 
545 #include <stdio.h>
546 
547 PyMODINIT_FUNC
PyInit_vallado_cpp(void)548 PyInit_vallado_cpp(void)
549 {
550     SatrecType.tp_name = "sgp4.vallado_cpp.Satrec";
551     SatrecType.tp_basicsize = sizeof(SatrecObject);
552     SatrecArrayType.tp_itemsize = 0;
553     SatrecType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
554     SatrecType.tp_doc = "SGP4 satellite record.";
555     SatrecType.tp_methods = Satrec_methods;
556     SatrecType.tp_members = Satrec_members;
557     SatrecType.tp_getset = Satrec_getset;
558     SatrecType.tp_new = PyType_GenericNew;
559 
560     if (PyType_Ready(&SatrecType) < 0)
561         return NULL;
562 
563     SatrecArrayType.tp_name = "sgp4.vallado_cpp.SatrecArray";
564     SatrecArrayType.tp_basicsize = sizeof(SatrecArrayObject);
565     SatrecArrayType.tp_itemsize = sizeof(elsetrec);
566     SatrecArrayType.tp_as_sequence = &SatrecArray_as_sequence;
567     SatrecArrayType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
568     SatrecArrayType.tp_doc = "SGP4 array of satellites.";
569     SatrecArrayType.tp_methods = SatrecArray_methods;
570     SatrecArrayType.tp_init = (initproc) SatrecArray_init;
571     SatrecArrayType.tp_new = SatrecArray_new;
572 
573     if (PyType_Ready(&SatrecArrayType) < 0)
574         return NULL;
575 
576     PyObject *m = PyModule_Create(&module);
577     if (m == NULL)
578         return NULL;
579 
580     Py_INCREF(&SatrecType);
581     if (PyModule_AddObject(m, "Satrec", (PyObject *) &SatrecType) < 0) {
582         Py_DECREF(&SatrecType);
583         Py_DECREF(m);
584         return NULL;
585     }
586 
587     Py_INCREF(&SatrecArrayType);
588     if (PyModule_AddObject(m, "SatrecArray", (PyObject *) &SatrecArrayType) < 0) {
589         Py_DECREF(&SatrecArrayType);
590         Py_DECREF(&SatrecType);
591         Py_DECREF(m);
592         return NULL;
593     }
594 
595     if (PyModule_AddIntConstant(m, "WGS72", (int)wgs72) ||
596         PyModule_AddIntConstant(m, "WGS72OLD", (int)wgs72old) ||
597         PyModule_AddIntConstant(m, "WGS84", (int)wgs84))
598         return NULL;
599 
600     return m;
601 }
602