satellogic / orbit-predictor

Python library to propagate satellite orbits.
MIT License
139 stars 43 forks source link

Make TLEPredictor behave more consistently upon pickling #107

Closed astrojuanlu closed 3 years ago

astrojuanlu commented 3 years ago

Edit: PersistedTLEPredictor and TLEPredictor have been merged, see below

Since tle is decorated with @reify, the behavior of TLEPredictor upon pickling/unpickling depends on whether .tle has been accessed at least once, or not. To demonstrate:

import datetime as dt
import pickle

from orbit_predictor.locations import Location
from orbit_predictor.predictors import TLEPredictor, PersistedTLEPredictor
from orbit_predictor.predictors.pass_iterators import SmartLocationPredictor
from orbit_predictor.sources import WSTLESource

SATE_ID = '41558U'  # newsat 2
db = WSTLESource("http://satellops.satellogic.com")
start = dt.datetime(2017, 3, 6, 7, 51)
end = start + dt.timedelta(days=5)

location = Location('bad-case-1', 11.937501570612568,
                    -55.35189435098657, 1780.674044538666)

predictor = TLEPredictor(SATE_ID, db)

with open("predictor_not_called.pkl", "wb") as f:
    pickle.dump(predictor, f)

l = list(predictor.passes_over(location, start, end, location_predictor_class=SmartLocationPredictor))
print(l[:2])

with open("predictor.pkl", "wb") as f:
    pickle.dump(predictor, f)

persisted_predictor = PersistedTLEPredictor(SATE_ID, db)

with open("persisted_predictor_not_called.pkl", "wb") as f:
    pickle.dump(persisted_predictor, f)

p_l = list(persisted_predictor.passes_over(location, start, end, location_predictor_class=SmartLocationPredictor))
print(l[:2])

with open("persisted_predictor.pkl", "wb") as f:
    pickle.dump(persisted_predictor, f)

And then, disconnecting from the Internet:

In [1]: import pickle                                                                             

In [2]: with open("predictor.pkl", "rb") as f:                                                       
   ...:     predictor = pickle.load(f)                                                               
   ...:                                                                                           

In [3]: with open("predictor_not_called.pkl", "rb") as f:                                         
   ...:     predictor_not_called = pickle.load(f)                                                 
   ...:                                                                                           

In [4]: with open("persisted_predictor.pkl", "rb") as f:                                          
   ...:     persisted_predictor = pickle.load(f)                                                  
   ...:                                                                                           

In [5]: with open("persisted_predictor_not_called.pkl", "rb") as f:                               
   ...:     persisted_predictor_not_called = pickle.load(f)                                       
   ...:                                                                                           
   ...:                                                                                           

In [6]: predictor.tle                                                                             
Out[6]: TLE(sate_id='41558U', lines=('1 41558U 16033C   20322.84292216  .00002488  00000-0  86947-
4 0  9999', '2 41558  97.4088  54.9794 0010754 259.5420 214.7873 15.30474986249344'), date=datetim
e.datetime(2020, 11, 18, 18, 59, 44, 806889))                                                     

In [7]: persisted_predictor.tle                                                                   
Out[7]: TLE(sate_id='41558U', lines=('1 41558U 16033C   20322.84292216  .00002488  00000-0  86947-
4 0  9999', '2 41558  97.4088  54.9794 0010754 259.5420 214.7873 15.30474986249344'), date=datetim
e.datetime(2020, 11, 18, 18, 59, 45, 299810))                                                                                                                                                       

In [8]: persisted_predictor_not_called.tle                                                        
Out[8]: TLE(sate_id='41558U', lines=('1 41558U 16033C   20322.84292216  .00002488  00000-0  86947-
4 0  9999', '2 41558  97.4088  54.9794 0010754 259.5420 214.7873 15.30474986249344'), date=datetim
e.datetime(2020, 11, 18, 18, 59, 45, 299810))                                                     

In [9]: predictor_not_called.tle                 
Exception requesting TLE: HTTPConnectionPool(host='satellops.satellogic.com', port=80): Max retrie
s exceeded with url: /api/tle/closest/?satellite_number=41558U&date=2020-11-18 (Caused by NewConne
ctionError('<urllib3.connection.HTTPConnection object at 0x7ff008ae26a0>: Failed to establish a ne
w connection: [Errno -2] Name or service not known'))

I also tested the performance, and it's exactly the same.

The only behavior difference would happen if the user creates a predictor and the first use is much later, on purpose. But honestly I don't think this is ever intended.

astrojuanlu commented 3 years ago

108