Python implementation of most recent version of the SGP4 satellite tracking algorithm.
- Documentation: https://pypi.python.org/pypi/sgp4/
- Download: https://pypi.org/project/sgp4/#files
- Changelog: https://pypi.org/project/sgp4/#changelog
Python version of the SGP4 satellite position library
License: MIT License
Python implementation of most recent version of the SGP4 satellite tracking algorithm.
...but sgp4.model.Satrec objects can:
In [1]: from sgp4.api import Satrec
In [2]: Satrec
Out[2]: sgp4.wrapper.Satrec
In [3]: line1, line2 = (
...: "1 00005U 58002B 00179.78495062 .00000023 00000-0 28098-4 0 4753",
...: "2 00005 34.2682 348.7242 1859667 331.7664 19.3264 10.82419157413667",
...: )
...: sat = Satrec.twoline2rv(line1, line2)
...:
In [4]: sat
Out[4]: <sgp4.wrapper.Satrec at 0x5649605d0430>
In [5]: import pickle
In [6]: with open("/tmp/save.pkl", "wb") as fp:
...: pickle.dump(sat, fp)
...:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-f3c37eb8fd19> in <module>
1 with open("/tmp/save.pkl", "wb") as fp:
----> 2 pickle.dump(sat, fp)
3
TypeError: cannot pickle 'Satrec' object
In [7]: from sgp4.model import Satrec
In [8]: sat = Satrec.twoline2rv(line1, line2)
In [9]: with open("/tmp/save.pkl", "wb") as fp:
...: pickle.dump(sat, fp)
# TLE for ISS
t1 = '1 25544U 98067A 12035.27140256 .00008132 00000-0 10934-3 0 9453'
t2 = '2 25544 051.6417 073.7902 0020652 004.6179 085.0245 15.58669131757138'
tle = twoline2rv(t1, t2, wgs72)
position, _ = tle.propagate(2012,2,4,7,55,00)
print position
# (-2395.0416350887344, 4440.7514261050846, 4492.3844513630256)
When I use http://sscweb.gsfc.nasa.gov/ I get the following coordinates:
-2377.74 4447.21 4495.19
These agree with the ones I get when using PyEphem for the same data.
Any ideas?
I am giving the master branch a try to see if my usage of sgp4 could be sped up. I am getting a "numba lowering error" (traceback below).
I suspect this is actually a numba bug rather than a problem with sgp4, but I haven't spent enough time with numba to know for sure. Have you been able to successfully use numba with sgp4? Have you considered using cython instead?
Failed at object (object mode frontend)
Caused By:
Traceback (most recent call last):
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/compiler.py", line 238, in run
stage()
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/compiler.py", line 599, in stage_objectmode_backend
self._backend(lowerfn, objectmode=True)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/compiler.py", line 576, in _backend
lowered = lowerfn()
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/compiler.py", line 550, in backend_object_mode
self.flags)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/compiler.py", line 882, in py_lowering_stage
lower.lower()
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/lowering.py", line 135, in lower
self.lower_normal_function(self.fndesc)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/lowering.py", line 176, in lower_normal_function
entry_block_tail = self.lower_function_body()
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/lowering.py", line 201, in lower_function_body
self.lower_block(block)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/lowering.py", line 216, in lower_block
self.lower_inst(inst)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/contextlib.py", line 35, in __exit__
self.gen.throw(type, value, traceback)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/errors.py", line 249, in new_error_context
six.reraise(type(newerr), newerr, sys.exc_info()[2])
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/errors.py", line 243, in new_error_context
yield
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/lowering.py", line 216, in lower_block
self.lower_inst(inst)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/objmode.py", line 131, in lower_inst
self.delvar(inst.value)
File "/home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/numba/objmode.py", line 534, in delvar
self._live_vars.remove(name)
LoweringError: 'sineo1'
File "../../scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/sgp4/propagation.py", line 1742
[1] During: lowering "del sineo1" at /home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/sgp4/propagation.py (1742)
Failed at object (object mode backend)
'sineo1'
File "../../scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/sgp4/propagation.py", line 1742
[1] During: lowering "del sineo1" at /home/broot/scratch/miniconda/envs/floodmappy4/lib/python2.7/site-packages/sgp4/propagation.py (1742)
Hello,
I have a doubt about your documentation concerning the Bstar term. Your comments say that it is in units kg / (m2 ER). But the TLE documentation says it is in ER^-1 units.
The definition of Bstar is Bstar = rho_0 * CD * A / (2 * m), if I am not wrong
I went to the function twoline2rv() and it seems that there is no conversion made. I hope you can enlighten me
Thank you
Hello,
Pardon my lack of proper terminology as I'm more of an artist than a satellite guru. As a result, I'll visualize as much as I can below.
I'm having some starting success plotting 15,147 orbiting objects around Earth, projected for a volunteer Science On a Sphere project at the museum setting at my office.
Cool!
The next challenge: Working on some alignment, comparing with ISS tle data, the orbit seems too "circular"/"ring-like", overlapping the same orbit over and over like this:
Here's what it looks like when I skip the re-projections and show in 3D space based on the values sgp4 is producing (a nice ring):
I'm used to seeing it look something like this (from a JavaScript project using this library):
Is it possible that I'm using the wrong values or functions to project this?
Here's a simplified version of the script with how I'm producing the position values:
from sgp4.earth_gravity import wgs72
from sgp4.io import twoline2rv
import datetime
def sat_pos_vel(sat, y, m, d, h, i, s):
position, velocity = sat.propagate(y, m, d, h, i, s)
return {"p":position, "v":velocity}
line1 = "1 25544U 98067A 15011.54761787 .00015493 00000-0 24903-3 0 7129"
line2 = "2 25544 051.6471 145.9316 0006026 238.8534 213.9008 15.53184119923669"
sat = twoline2rv(line1, line2, wgs72)
when = datetime.datetime.now()
for i in range(300):
sat_prop = sat_pos_vel(sat, when.year, when.month, when.day, when.hour, when.minute, when.second)
when = when + datetime.timedelta(0,100) #add 100 seconds
print(sat_prop)
I'm using Python 3.4
Let me know if I can provide additional information.
Thanks for taking a look in advance! I truly appreciate it.
The TLE FAQ specifies that:
Fields 2.3, 2.4, 2.6, and 2.7 all have units of degrees and can range from 0 up to 360 degrees—field 2.3 (inclination) only goes up to 180 degrees.
However, if one creates a Satrec
object with, say, argpo=-1.90861
:
sgp4init
does not complainrv2twoline
produces an invalid TLE!For example:
1 99510U 20999B 20311.53996093 .00007856 00000-0 10000-4 0 9995
2 99510 97.2675 23.7519 0007535 -109.3554278.9614 15.31068184 67
Should python-sgp4 perform any sort of validation?
as I heavily rely on skyfield and the linked sgp4 package and I would like to move the on top build applications to support python3.9. Is there any planning, when the sgp4 distro also brings the precompiled packages with it?
many thanks for this great packages.
Michel
In a3c8da1, executing the Vanguard 1 example in __init__.py
:
>>> from sgp4.earth_gravity import wgs72
>>> from sgp4.io import twoline2rv
>>>
>>> line1 = ('1 00005U 58002B 00179.78495062 '
... '.00000023 00000-0 28098-4 0 4753')
>>> line2 = ('2 00005 34.2682 348.7242 1859667 '
... '331.7664 19.3264 10.82419157413667')
>>>
>>> satellite = twoline2rv(line1, line2, wgs72)
IndexError Traceback (most recent call last)
<ipython-input-12-8ec3d1719ba6> in <module>()
----> 1 satellite = twoline2rv(line1, line2, wgs72)
/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/sgp4/io.pyc in twoline2rv(longstr1, longstr2, whichconst, afspc_mode)
150 if longstr1[62] == ' ':
151 longstr1[62] = '0';
--> 152 if longstr1[68] == ' ':
153 longstr1[68] = '0';
154
IndexError: list index out of range
Works in v1.3 (4266bfc ), but haven't bisected to find culprit.
The following function returns a fractional number (apparently ending in 0.5) for jd, which probably means that the fr is wrong too.
Lines 17 to 21 in 2e1a06f
Have a fix (based on ext.jday) in an incoming PR.
Just wanted to raise this issue while it stands true. The version history on PyPi shows this: https://pypi.org/project/sgp4/#history
If the PyPi version could be updated, that would make it easier for others to use the latest improvements of this library. Thanks!
In the 2.0 extension, sgp4 has been modified (perhaps too conveniently) to take arguments of a different from from the previous python version, and the SGP4.CPP function itself.
python-sgp4/extension/wrapper.cpp
Lines 126 to 127 in 2e1a06f
This could be preserved as an "sgp4_jd()" convenience function.
I have an incoming PR which reverts this.
Additionally, the proliferation of the jd, fr separation for julian dates perhaps creates more problems than it solves. Opening a separate Issue to discuss that.
TLEs that only differ in inclination or RAAN of very small angles (~0.001 degrees or less) when propagated using SGP4 seem to yield identical positions. Inclination or RAAN has to be adjusted to greater degree to yield a change. Seems like there's some loss of precision somewhere...
Will add example code to recreate error tomorrow.
Yes, the documentation and changelog are here:
https://pypi.org/project/sgp4/
So that I can make the experience more convenient for users: where did you look that lacked a link to the documentation? I'll be happy to add more links to the docs to help folks find them!
Originally posted by @brandon-rhodes in skyfielders/python-skyfield#30 (comment)
Hi, I'm getting this output when executing sgp4/tests.py
without unittest2 installed,
❯ python tests.py
EE...E..
======================================================================
ERROR: test_bad_first_line (__main__.Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests.py", line 135, in test_bad_first_line
with self.assertRaisesRegex(ValueError, re.escape("""TLE format error
AttributeError: 'Tests' object has no attribute 'assertRaisesRegex'
======================================================================
ERROR: test_bad_second_line (__main__.Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests.py", line 147, in test_bad_second_line
with self.assertRaisesRegex(ValueError, re.escape("""TLE format error
AttributeError: 'Tests' object has no attribute 'assertRaisesRegex'
======================================================================
ERROR: test_mismatched_lines (__main__.Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests.py", line 160, in test_mismatched_lines
with self.assertRaisesRegex(ValueError, re.escape(msg)):
AttributeError: 'Tests' object has no attribute 'assertRaisesRegex'
----------------------------------------------------------------------
Ran 8 tests in 0.036s
FAILED (errors=3)
However, everything is fine when test suite loads unittest2 package,
❯ python tests.py
........
----------------------------------------------------------------------
Ran 8 tests in 0.039s
OK
THX
In your implementation of sgp4, in propagation.py, you have the following "fix":
# sgp4fix for decaying satellites
if mrt < 1.0:
satrec.error_message = ('mrt {0:f} is less than 1.0 indicating'
' the satellite has decayed'.format(mrt))
satrec.error = 6;
return false, false;
Returning (false,false) discards the calculated values, a behavior I find to be rather unhelpful - although the state is invalid, it is still useful, e.g. for solving for impact time and location via Newton's method. Could you return the calculated values instead? You are already indicating the error condition via error and error_message.
The docs say epochyr: Full four-digit year of this element set's epoch moment.
but in fact it just uses the TLE number, which is 2 digits, for example instead of 2007, epochyr is just 7.
Apart from that, it would be nice to get the epoch date as a simple datetime object.
I was trying to use sgp4 to construct tles from orbital parameters using latest version (2.12)
I am getting invalid tles back, however. This seems to be because export_tle expects epochyr and epochdays to be set, but they don't seem to be set by sgp4init.
Maybe this is user error, but I couldn't quite figure it out.
Here's the test I constructed (using the sgp4init example from the documentation):
from sgp4.api import WGS72, Satrec
from sgp4.exporter import export_tle
satrec = Satrec()
satrec.sgp4init(
WGS72, # gravity model
'i', # 'a' = old AFSPC mode, 'i' = improved mode
5, # satnum: Satellite number
18441.785, # epoch: days since 1949 December 31 00:00 UT
2.8098e-05, # bstar: drag coefficient (/earth radii)
6.969196665e-13, # ndot: ballistic coefficient (revs/day)
0.0, # nddot: second derivative of mean motion (revs/day^3)
0.1859667, # ecco: eccentricity
5.7904160274885, # argpo: argument of perigee (radians)
0.5980929187319, # inclo: inclination (radians)
0.3373093125574, # mo: mean anomaly (radians)
0.0472294454407, # no_kozai: mean motion (radians/minute)
6.0863854713832, # nodeo: right ascension of ascending node (radians)
)
tle = export_tle(satrec)
assert tle[0][18:32] != '00000.00000000'
The sgp4 C++ reference library has a very useful Eci::ToGeodetic()
method (see https://github.com/dnwrnr/sgp4/blob/0cc181ee50c707d1a433e97be98de2b925dd78a4/libsgp4/Eci.cpp).
This is very useful for converting a propagated ephemeris into latitude/longitude values. As far as I can see, this functionality does not exist in this python version. Is there any plan to, or was there a specific reason why this was not incorporated?
The Javascript version also has this functionality (https://github.com/shashwatak/satellite-js).
Hello,
I am developing an orbit propagator for a thesis and i am trying to check the result i am getting with STK propagator.
I am finding that the results not match, and i have seen that SGP4.propagate returns the position and velocity vectors in TEME reference system. I am trying to get it in the ECEF reference system but i am quite new at this, and i am not getting it.
Is there any way to perform that conversion directly through sgp4 or some other library ?
Any help is more than welcome.
Thank you so much in advance.
Hi,
Not sure where else to ask about this, but here goes. I see that this package is listed on PyPI, but it would be useful for me to have it accessible with Conda (i.e. conda install sgp4
) -- specifically, for my Julia wrapper of this package. Apparently there is a way to do this, but it requires that someone hosts the Conda package once it's created.
I am happy to try and figure this out and host it, but it's not clear to me that you (as the original author) or this Github repository is clearly indicated once it's hosted on the "Anaconda cloud" (formerly binstar, I think?). So, I figured I'd give you the chance to do it yourself, if you'd like. Thanks, either way.
Chris
How can I get a semi-major axis of the satellite? I have found a wrapper method called "initl" on "propagation.py", but did not understand how to use it in order to get the ao. Could you, please, give an example?
I believe the following is a bug in the Vallado source code, when initializing Satrec()
variables in sgp4init()
:
python-sgp4/extension/SGP4.cpp
Lines 1449 to 1450 in 2a365a3
These variables aren't used elsewhere within the code, so the fact that they all take the last-set value as follows doesn't cause any internal problems:
python-sgp4/extension/SGP4.cpp
Line 1870 in 2a365a3
However, anyone accessing them externally (for example, to reset the epoch of the osculating elements) will find that they all equal satrec.nm
Hello,
I started to play with tracking the ISS space station.
Have approached it with Pyhem and SGP4 1.4.
When I do the calculation I get a different answer for the longitude.
I have checked the results with a tracker found on the internet, and the value of the calculation with Pyephem are the same!
I use the ECEF routine to convert the (x,y,z) to lat,lon and alt.
Thanks for any support,or help.
Jan Kromhout
Hellevoetsluis-NL
import urllib.request
from sgp4.earth_gravity import wgs84
from sgp4.io import twoline2rv
from datetime import datetime
from math import sqrt, atan2, sin, cos, pi
import ephem
def ecef(x, y, z): #Convert (x,y,z) to (lat,lon,alt)
a = 6378137
e = 8.1819190842622e-2
b = sqrt(a ** 2 * (1 - e ** 2))
ep = sqrt((a ** 2 - b ** 2) / (b ** 2))
p = sqrt(x ** 2 + y ** 2)
th = atan2(a * z, b * p)
lon = atan2(y,x)
lat = atan2((z + ep ** 2 * b * sin(th) ** 3), (p - e ** 2 * a * cos(th) ** 3))
n = a / sqrt(1 - e ** 2 * sin(lat) ** 2)
alt = p / cos(lat) - n
lon = lon % (2 * pi)
return lat_180/pi, lon_180/pi, alt
def update_tle():
data=[]
#Schrijf data tijdelijk in file
urllib.request.urlretrieve('http://www.celestrak.com/NORAD/elements/stations.txt','test.dat')
f=open('test.dat','r')
data=f.read()
data=data.split('\n')
return data[0],data[1],data[2] #Gegevens International Space Station
print('Read stations data')
name,line1,line2=update_tle()
print(name)
print(line1)
print(line2)
satellite = twoline2rv(line1, line2, wgs84)
now=datetime.utcnow()
print ('UTC time ',now)
print('Calculate with sgp4 1.4')
position, velocity = satellite.propagate(now.year,now.month,now.day,now.hour,now.minute,now.second)
lat,lon,alt=ecef(position[0]_1000,position[1]_1000,position[2]_1000)
speed=sqrt(velocity[0]__2+velocity[1]__2+velocity[2]__2)_3600
print('epochyr ',satellite.epochyr)
print('epochdays ',satellite.epochdays)
print('jdsatepoch ',satellite.jdsatepoch)
print('epoch ',satellite.epoch)
print('position (km) ',position)
print('velocity (km/s) ',velocity)
print('orbital speed (km/h) ',speed)
print()
print('Position of ISS at time ',now)
print('Latitude (deg) ',lat)
print('Longitude(deg) ',lon)
print('Height ',alt)
home = ephem.Observer()
home.lon = '52' # +E
home.lat = '05' # +N
iss= ephem.readtle(name, line1, line2)
iss.compute(now)
lat=ephem.degrees(iss.sublat)
lon=ephem.degrees(iss.sublong)
print('Calculate with Ephem')
print('Latitude (deg) ',float(lat)_180/pi)
print('Longitude (deg) ',float(lon)_180/pi)
The reference implementation of sgp4 hasn't changed significantly in a decade. It should be pretty straightforward (if tedious) to reimplement future changes in python, especially if they provide test cases.
To that end, I'm starting to rename variables but we should probably have a discussion about when to pep8 the code.
https://github.com/ckuethe/python-sgp4/commits/more_pythonic
While investigating skyfielders/python-skyfield#30 I found a few places where an API break would be necessary to fully accelerate the function. In particular, passing in a string as a flag and comparing it to a constant eg. _dpper does not optimize well. Potentially better results could be obtained by using integer or boolean parameters.
Also, Numba doesn't support try-except in jitted code. That might not be a big problem if sgp4 is compiled early rather than lazy, since numba is only needed at build time and not runtime. That may also save some of the horrible compilation time.
Thoughts?
All versions run in accelerated mode (always True)
Situation could be replicated just with that script. So we could move to SGP4 issues
Mac Terminal says:
LANG=de_DE.UTF-8
PyCharm says:
LC_CTYPE=de_DE.UTF-8
I had a conversation with the user with the division by zero error. As soon as he switched his language setting from German to English, the problem went away.
Another interesting point about: The error is visible only when running pytest with this script from the terminal. If I start the python interpreter and paste the script manually and let it run in the terminal, It succeeds without problems.
The distribution on pypi should include a LICENSE
file. I need this to satisfy the requirement to install a copy of the license for this package using the PKGBUILD
I maintain for Arch Linux. To wit, namcap
currently gives the following error:
python-sgp4 E: Missing custom license directory (usr/share/licenses/python-sgp4)
See also, note on why this is motivated: https://wiki.archlinux.org/index.php/PKGBUILD#license
Moved to skyfielders/python-skyfield#167.
Cf. skyfielders/python-skyfield#411
[ 37s] ________________ test_all_three_gravity_models_with_twoline2rv _________________
[ 37s] > assert_wgs72old(Satrec.twoline2rv(LINE1, LINE2, WGS72OLD))
[ 37s] E AssertionError: -3754.2514732427567 != -3754.251473242793 within 12 places (3.637978807091713e-11 difference)
[ 37s] _________________ test_all_three_gravity_models_with_sgp4init __________________
[ 37s] sat.sgp4init(WGS72OLD, 'i', VANGUARD_ATTRS['satnum'], VANGUARD_EPOCH, *args)
[ 37s] > assert_wgs72old(sat)
[ 37s] E AssertionError: -3754.2514732427567 != -3754.251473242793 within 12 places (3.637978807091713e-11 difference)
[ 50s] > assert_wgs84(Satrec.twoline2rv(LINE1, LINE2, WGS84))
[ 50s] E AssertionError: 4719.227897029575 != 4719.227897029576 within 12 places (9.094947017729282e-13 difference)
[ 50s] sat.sgp4init(WGS84, 'i', VANGUARD_ATTRS['satnum'], VANGUARD_EPOCH, *args)
[ 50s] > assert_wgs84(sat)
[ 50s] E AssertionError: 4719.227897029575 != 4719.227897029576 within 12 places (9.094947017729282e-13 difference)
Hi,
the Grace-1 and Grace-2 TLE are returning NaN's.
`
from sgp4.earth_gravity import wgs72,wgs72old,wgs84
from sgp4.io import twoline2rv
import sgp4
line1 = ('1 27392U 02012B 17317.13935224 .00120532 42499-5 34060-3 0 9998')
line2 = ('2 27392 88.9800 298.9359 0008907 105.9560 254.2707 15.93313173881025')
satellite = twoline2rv(line1, line2, wgs72)
position, velocity = satellite.propagate(2000, 6, 29, 12, 50, 20.1)
print(position)
print(velocity)
print(satellite.error_message)
(nan, nan, nan)
(nan, nan, nan)
mean eccentricity -0.025182 not within range 0.0 <= e < 1.0
`
I confirmed the TLE from multiple sources online. I'm using this for a 3d visualisation and am very much out of my depth as to what is going on under the hood to figure out where the error could originate.
Best regards,
Fabian
STEPS TO REPRODUCE
#11 96.93 ERROR: Could not find a version that satisfies the requirement sgp4==2.4 (from -r /tmp/requirements.txt (line 73)) (from versions: 1.0, 1.1, 1.3, 1.4, 2.0, 2.2, 2.3)
#11 97.35 ERROR: No matching distribution found for sgp4==2.4 (from -r /tmp/requirements.txt (line 73))
#11 ERROR: executor failed running [bash -c export FROM_DOCKERFILE=1; export PIP_FIND_LINKS=wheels/alpine; pushd /run/secrets && cp .env.tmp /.env && popd && chmod a+x ./helper.sh && apk add --no-cache --virtual .build-deps gcc g++ libffi-dev linux-headers make musl-dev openssl-dev pcre-dev postgresql-dev python3-dev && ./helper.sh build-and-sync-wheels && pip install -r /tmp/requirements.txt -r /tmp/requirements-dev.txt && pip install docker-compose==1.25.0 && pip install pyyaml==5.1.2 && apk --purge del .build-deps && rm -fv .env]: runc did not terminate sucessfully
------
> [evp-pp-helper 5/6] RUN --mount=type=cache,target=./wheels --mount=type=secret,id=.env.tmp export FROM_DOCKERFILE=1; export PIP_FIND_LINKS=wheels/alpine; pushd /run/secrets && cp .env.tmp /.env && popd && chmod a+x ./helper.sh && apk add --no-cache --virtual .build-deps gcc g++ libffi-dev linux-headers make musl-dev openssl-dev pcre-dev postgresql-dev python3-dev && ./helper.sh build-and-sync-wheels && pip install -r /tmp/requirements.txt -r /tmp/requirements-dev.txt && pip install docker-compose==1.25.0 && pip install pyyaml==5.1.2 && apk --purge del .build-deps && rm -fv .env:
------
failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to solve with frontend gateway.v0: rpc error: code = Unknown desc = failed to build LLB: executor failed running [bash -c export FROM_DOCKERFILE=1; export PIP_FIND_LINKS=wheels/alpine; pushd /run/secrets && cp .env.tmp /.env && popd && chmod a+x ./helper.sh && apk add --no-cache --virtual .build-deps gcc g++ libffi-dev linux-headers make musl-dev openssl-dev pcre-dev postgresql-dev python3-dev && ./helper.sh build-and-sync-wheels && pip install -r /tmp/requirements.txt -r /tmp/requirements-dev.txt && pip install docker-compose==1.25.0 && pip install pyyaml==5.1.2 && apk --purge del .build-deps && rm -fv .env]: runc did not terminate sucessfully
The sgp4 model used to have a property .epoch that returned a python datetime object. This seems to have been removed. It's also been removed from releases of 2.3 so it's not available if I install a past version. There doesn't seem to be any function for getting a datetime from the jsatepoch and jsatepochF. Sorry if the issue is at my end
In the v2.0 updates, the epoch has been split to its integer and fractional components, apparently in an effort to maintain compatibility with the SGP.CPP code. However, that code also doesn't consistently use it -- and the separation of the pieces have no apparent benefit (to me at least).
Is there a reason that the user must be made to manage these variables in their day and day-fraction components?
Hello, this may be a fairly simple question, as I'm just starting Python, coming from Matlab, but here goes:
I get the error:
File "C:\ProgramData\Anaconda3\lib\site-packages\sgp4\io.py", line 131, in twoline2rv
assert line.startswith('1 ')
TypeError: startswith first arg must be bytes or a tuple of bytes, not str
When trying to run the code. I have tried to change the '1' in the startswith function to a different type by adding a b before the '1', by using bytes('1'), by using tuple('1'), and by using str.encode('1'), but I continue to get the exact same error.
Any ideas? Thanks!
The format of TLEs has changed because the space fence is going to rapidly overwhelm the 5-digit numbers available in the old format. This needs to be supported by all the satellite libraries that support TLEs.
For example, catalog number 270000 (which is an analyst object seen by the space fence) has a catnum field of T00000
as in:
1 T0000U 20341.14572529 .00000446 00000-0 15605-2 0 9998
2 T0000 90.2902 300.0888 0031941 22.1325 338.1165 12.95152933 48676
This range is in given by the RESTFUL link https://www.space-track.org/basicspacedata/query/class/gp/EPOCH/%3Enow-30/NORAD_CAT_ID/270000--339999/orderby/NORAD_CAT_ID/format/tle
From space-track:
Alpha-5 is a stopgap object numbering schema from the United States Space Force that increases the satellite catalog’s capacity to display up to 339,999 objects in the GP/GP_History API classes using legacy fixed-width Two and Three Line Element Set (TLE/3LE) formats.
Replacing the 1st digit of the 5-digit object number with an alphanumeric character makes it possible to represent 240,000 more numbers. Objects less than 100,000 are unaffected by Alpha-5, as are users who download elsets from the GP and GP_History API classes in other formats like XML, JSON, KVN, and CSV. In order to preserve legacy operations that depend on 5-digit integers, our legacy API Classes tle, tle_latest, and tle_publish will not change to Alpha-5.
Only capital letters and numbers are used in Alpha-5. The letters “I” and “O” are omitted to avoid confusion with the numbers “1” and “0”.
While studying #70, I discovered a roundtrip problem with export_tle
:
>>> lines = """1 46272U 20061A 20311.82207422 .00000083 00000-0 68546-5 0 9999
... 2 46272 97.3995 23.7575 0006827 352.5560 7.5569 15.20743438 9810"""
>>> print(lines)
1 46272U 20061A 20311.82207422 .00000083 00000-0 68546-5 0 9999
2 46272 97.3995 23.7575 0006827 352.5560 7.5569 15.20743438 9810
>>> sat = twoline2rv(*lines.splitlines(), wgs84)
>>> print("\n".join(export_tle(sat)))
1 46272U 20061A 2020311.82207422 .00000083 00000-0 68546-5 0 9993
2 46272 97.3995 23.7575 0006827 352.5560 7.5569 15.20743438 9810
And I think it also happens with other examples in https://github.com/brandon-rhodes/python-sgp4/blob/master/sgp4/SGP4-VER.TLE.
I still didn't have time to craft a proper reproducible example, but we observed a noticeable slowdown when using sgp4 2.2, which is entirely consistent with this observation by @rirze:
In my course of using this library, I have found Numba to be increasing my runtimes for the sgp4 routine, rather than decreasing it. [...] In fact, on my machine, Numba compilation increases sgp4 runtime by 2-3x! (Ubuntu 18.04 Windows Subsystem Linux, Python 3.6.4)
We are also observing a large number of warnings:
tests/test_full_pipeline.py::test_orbit_assigners_are_recovered
/home/juanlu/.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/sgp4/propagation.py:1698: NumbaWarning:
Compilation is falling back to object mode WITH looplifting enabled because Function "sgp4" failed type inference due to: Untyped global name '_dpper': cannot determine Numba type of <class 'function'>
File "../../../../.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/sgp4/propagation.py", line 1838:
def sgp4(satrec, tsince, whichconst=None):
<source elided>
ep, xincp, nodep, argpp, mp = _dpper(
^
@jit(cache=True)
tests/test_full_pipeline.py::test_orbit_assigners_are_recovered
/home/juanlu/.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/sgp4/propagation.py:1698: NumbaWarning:
Compilation is falling back to object mode WITHOUT looplifting enabled because Function "sgp4" failed type inference due to: Untyped global name '_dpper': cannot determine Numba type of <class 'function'>
File "../../../../.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/sgp4/propagation.py", line 1838:
def sgp4(satrec, tsince, whichconst=None):
<source elided>
ep, xincp, nodep, argpp, mp = _dpper(
^
@jit(cache=True)
tests/test_full_pipeline.py::test_orbit_assigners_are_recovered
/home/juanlu/.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/numba/object_mode_passes.py:178: NumbaWarning: Function "sgp4" was compiled in object mode without forceobj=True, but has lifted loops.
File "../../../../.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/sgp4/propagation.py", line 1700:
#@jit
def sgp4(satrec, tsince, whichconst=None):
^
state.func_ir.loc))
tests/test_full_pipeline.py::test_orbit_assigners_are_recovered
/home/juanlu/.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/numba/object_mode_passes.py:187: NumbaDeprecationWarning:
Fall-back from the nopython compilation path to the object mode compilation path has been detected, this is deprecated behaviour.
For more information visit http://numba.pydata.org/numba-doc/latest/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit
File "../../../../.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/sgp4/propagation.py", line 1700:
#@jit
def sgp4(satrec, tsince, whichconst=None):
^
warnings.warn(errors.NumbaDeprecationWarning(msg, state.func_ir.loc))
tests/test_full_pipeline.py::test_orbit_assigners_are_recovered
/home/juanlu/.pyenv/versions/3.7.4/envs/mbp37_4/lib/python3.7/site-packages/sgp4/propagation.py:1698: NumbaWarning: Cannot cache compiled function "sgp4" as it uses lifted loops
@jit(cache=True)
sgp4 own tests do take much longer now, but this is somewhat to be expected because the compilation step takes some time:
$ time python -m sgp4.tests 2> /dev/null
real 0m12,655s
user 0m12,631s
sys 0m0,537s
$ export NUMBA_DISABLE_JIT=1
$ time python -m sgp4.tests
...............
----------------------------------------------------------------------
Ran 15 tests in 0.080s
OK
real 0m0,678s
user 0m0,724s
sys 0m0,228s
in particular, one might try to do something like propagate(608400)
which might seem reasonable given that the sgp4 function is called assgp4(satrec, seconds_since_epoch)
and instead of propagating the satellite a week from the epoch, propagate will try compute for the year 608400.
of course I'd never do something like that... 🤨
Perhaps a better interface for propagate would be propagate(self, obs_time=None, second_since_epoch=None)
.
If neither obs_time nor seconds_since epoch are given, then propagate would compute at obs_time=datetime.datetime.now()
- at the time the function was called. If obs_time is None, and seconds_since_epoch is a real number, then compute based on the epoch of the TLE. If both are given... that should probably be an error because it's not immediately clear what's supposed to happen.
The fix would be pretty minimal, and it would be convenient, but I wasn't sure if there was a reason not to.
if isinstance(year, datetime):
year, month, day, hour, minute, second = year.timetuple([:6])
second += year.microsecond / 1.0e6
This would go here in the model propagate function. month, day, hour, minute, second would all be optional with defaults.
While Satrec.twoline2rv() allows creating a record from TLEs, CPP-accelerated functionality is missing for creating records directly from element variables.
When running tests in a different time zone (I am in CET), some tests fail:
Failed tests: testNextSatPass(uk.me.g4dpz.satellite.PassPredictorTest): expected:<2009-01-05T0[4:28:10+00]00> but was:<2009-01-05T0[5:28:10+01]00>
testNextSatPassWithWindBack(uk.me.g4dpz.satellite.PassPredictorTest): expected:<2009-01-05T0[4:28:10+00]00> but was:<2009-01-05T0[5:28:10+01]00>
correctToStringResult(uk.me.g4dpz.satellite.PassPredictorTest): expected:<...5, 2009(..)
poleIsPassed(uk.me.g4dpz.satellite.PassPredictorTest): expected:<2009-01-05T0[7:42:45+00]00, north> but was:<2009-01-05T0[8:42:45+01]00, north>
Tests run: 32, Failures: 4, Errors: 0, Skipped: 0
[root@docs orbital]# pip install sgp4
Requirement already satisfied: sgp4 in /usr/lib/python2.7/site-packages
[root@docs orbital]# python sgp4.py
Traceback (most recent call last):
File "sgp4.py", line 4, in <module>
from sgp4.earth_gravity import wgs84
File "/usr/share/nginx/html/orbital/sgp4.py", line 4, in <module>
from sgp4.earth_gravity import wgs84
ImportError: No module named earth_gravity
What could be the problem?
In unaccelerated version, the days2mdhms()
function can return a value with the previous minute and 60 seconds. for example, day=133.35625 should give a time of 08:33:00
but instead gives 08:32:60
. And the 60 is not 59.99999999 but a solid 60. When used in a TLE, this causes TLE interpretation to break.
In [1]: from sgp4.api import accelerated, days2mdhms
In [2]: print(accelerated)
False
In [3]: print(days2mdhms(2020, 133.35625))
(5, 12, 8, 32, 60.0)
In [4]: _,_,_,_,s = days2mdhms(2020, 133.35625)
In [5]: print(s, s==60)
60.0 True
Therefore, in Skyfield
EarthSatellite('1 00000U 00000ZZ 20133.35625000 .00000000 00000-0 00000-0 0 9990',
'2 00000 24.6020 12.0680 3978520 69.4580 328.0792 0.99945723 08')
gives ValueError: second must be in 0..59
:
Traceback (most recent call last):
File ".../lib/python3.8/site-packages/sgp4/io.py", line 101, in twoline2rv
def twoline2rv(longstr1, longstr2, whichconst, opsmode='i', satrec=None):
ValueError: second must be in 0..59
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File ".../lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 3293, in run_code
async def run_code(self, code_obj, result=None, *, async_=False):
File "<ipython-input-24-830190fd66c5>", line 1, in <module>
EarthSatellite('1 00000U 00000ZZ 20133.35625000 .00000000 00000-0 00000-0 0 9990',
File ".../lib/python3.8/site-packages/skyfield/sgp4lib.py", line 89, in __init__
def __init__(self, line1, line2, name=None, ts=None):
File ".../lib/python3.8/site-packages/sgp4/model.py", line 49, in twoline2rv
@classmethod
File ".../lib/python3.8/site-packages/sgp4/io.py", line 101, in twoline2rv
def twoline2rv(longstr1, longstr2, whichconst, opsmode='i', satrec=None):
ValueError: second must be in 0..59
The error is where days2mdhms rounds off the seconds to 6 digits, which can round upwards from 59.99... .
I've implemented the SGP4 algorithm. It works OK for LEO satellites.
However, for Molniya 1-29 satellite there is a strange trajectory: it's not elliptic, looks like 8.
Do you have any idea?
The new Satrec
class uses the WGS-72 gravity model:
python-sgp4/extension/wrapper.cpp
Line 113 in 10e8076
Line 47 in 10e8076
There is some discussion in Vallado et al. "Revisiting Spacetrack Report #3: Rev 1" but I haven't fully understood it yet. Do you have any supporting documentation that says that WGS-72 should always be used, or do you think it could be left as a parameter?
# Retrieved from http://celestrak.com/NORAD/elements/supplemental/iss.txt
# "Last-modified" header = Mon, 30 Dec 2019 15:36:08 GMT
>>> l0 = 'ISS [Orbit 606]'
>>> l1 = '1 25544U 98067A 19366.82137887 .00016717 00000-0 10270-3 0 9129'
>>> l2 = '2 25544 51.6392 96.6358 0005156 88.7140 271.4601 15.49497216 6061'
>>> rv = twoline2rv(l1, l2, wgs72)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-26-09487e861a12> in <module>()
5 l2 = '2 25544 51.6392 96.6358 0005156 88.7140 271.4601 15.49497216 6061'
6
----> 7 rv = twoline2rv(l1, l2, wgs72)
/usr/local/lib/python3.7/dist-packages/sgp4/io.py in twoline2rv(longstr1, longstr2, whichconst, opsmode, satrec)
225 satrec.jdsatepoch = jday(year,mon,day,hr,minute,sec);
226 satrec.epoch = datetime(year, mon, day, hr, minute, int(sec_whole),
--> 227 int(sec_fraction * 1000000.0 // 1.0))
228
229 # ---------------- initialize the orbit at sgp4epoch -------------------
ValueError: day is out of range for month
Whaaaaa? I generally trust Celestrak to not produce broken files (this is literally the first time I've seen this), a bunch of other TLEs from that file do parse correctly, and the error doesn't tell me exactly what the invalid value is, just that I now have to go break out the TLE definition to try figure it out.
>>> from sgp4.ext import days2mdhms, jday
>>> jd = 19366.82137887
>>> day = jd % 1000
>>> year = 2000 + int(jd/1000)
>>> print(days2mdhms(year, day))
(12, 32, 19, 42, 47.13436799909687)
Oh. December 32nd. This is fine. When if I fix the year and day by hand it works.
>>> print(days2mdhms(year+1, day-365))
(1, 1, 19, 42, 47.13436811696738)
Would it make sense to wrap the datetime()
in an exception handler to say what parameter was invalid ? Would it make sense to include that end-of-year handling inside days2mdhms
?
As documented at Celestrak's website, an XML/JSON based format called OMM (spec: https://public.ccsds.org/Pubs/502x0b2c1.pdf) is being pushed as a more modern replacement for the TLE format. Since the parameters are still reported in terms of sgp4 elements, and given the dominance of CelesTrak as a source of orbit information, it seems reasonable imo to add support to this project for the OMM format.
I'd be happy to open a PR for this with some guidance on how you'd like this structured. Would it be more reasonable to:
io.py
?omm.py
or something similar?This is my first time trying to contribute to an open-source project, so apologies if there's something I'm missing.
I'm propagating the orbit for 2 days time interval.
Is there a way to get the intermediate points of propagation with 1 second periodicity?
I have an application for which I need to be able to produce TLE's from the mean elements in the "model" member of a Skyfield EarthSatellite object. After implementing the text-rendering function, I tried round-tripping a TLE through it and noted that the code didn't reproduce the mean-motion term correctly.
Tracing through twoline2rv() eventually led to the section of the sgp4.propagation._initl()
function that "un-Kozai's" the mean motion, which explained the difference. I tried copying the calculation of the "del_" scale factor into my routine to "undo" the Kozai --> Brouwer transformation, but unfortunately it's a non-conservative transform b/c the first intermediate variable in the del_
calculation ("ak") depends on the input mean-motion value.
Would it be possible to add a utility function to perform the inverse (Brouwer --> Kozai) transformation on mean motion?
I see in the PyPI package that python-sgp4 is licensed under MIT, but it would be useful to state it in the README and even include a LICENSE or COPYING file.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.