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