File indexing completed on 2024-12-01 12:29:09
0001 # SPDX-License-Identifier: LGPL-2.1-or-later 0002 # 0003 # SPDX-FileCopyrightText: 2012 Rene Kuettner <rene@bitkanal.net> 0004 # 0005 0006 from __future__ import print_function 0007 0008 from SpaceObjectCatalog import HorizonsSpaceObject, TASCSpaceObject 0009 0010 import sys 0011 import telnetlib 0012 import time 0013 import httplib 0014 import urllib 0015 import calendar 0016 import re 0017 0018 0019 class HorizonsDownloader(object): 0020 0021 """A simple telnet client for the horizons system.""" 0022 0023 _body_ids = { 0024 'Mercury' : 199, 0025 'Venus' : 299, 0026 'Earth' : 399, 0027 'Mars' : 499, 0028 'Jupiter' : 599, 0029 'Saturn' : 699, 0030 'Uranus' : 799, 0031 'Neptune' : 899, 0032 } 0033 0034 _commands = { 0035 re.escape("$$SOE"): 'NoResponse', 0036 re.escape("$$EOE"): 'Data', 0037 re.escape("Horizons> "): 'Done', 0038 re.escape("Continue [ <cr>=yes, n=no, ? ] : "): "n", 0039 re.escape("[E]phemeris, [F]tp, [M]ail, [R]edisplay, ?, <cr>: "): "e", 0040 re.escape("Observe, Elements, Vectors [o,e,v,?] : "): "v", 0041 re.escape("Coordinate center [ <id>,coord,geo ] : "): "500@{body_id}", 0042 re.escape("Confirm selected station [ y/n ] --> "): "y", 0043 re.escape("Reference plane [eclip, frame, body ] : "): "frame", 0044 re.escape("Output interval [ex: 10m, 1h, 1d, ? ] : "): "{interval_days}d", 0045 re.escape("Accept default output [ cr=(y), n, ?] : "): "n", 0046 re.escape("Output reference frame [J2000, B1950] : "): "J2000", 0047 re.escape("Corrections [ 1=NONE, 2=LT, 3=LT+S ] : "): "1", 0048 re.escape("Output units [1=KM-S, 2=AU-D, 3=KM-D] : "): "1", 0049 re.escape("Spreadsheet CSV format [ YES, NO ] : "): "YES", 0050 re.escape("Label cartesian output [ YES, NO ] : "): "NO", 0051 re.escape("Select output table type [ 1-6, ? ] : "): "2", 0052 re.escape("[A]gain, [N]ew-case, [F]tp, [K]ermit, [M]ail, [R]edisplay, ? : "): "n", 0053 re.escape("Use previous center [ cr=(y), n, ? ] : "): "n", 0054 re.compile("Starting CT .* : "): "{datetimestart}", 0055 re.compile("Ending CT .* : "): "{datetimeend}", 0056 } 0057 0058 def __init__(self): 0059 super(HorizonsDownloader, self).__init__() 0060 self._host = "horizons.jpl.nasa.gov" 0061 self._port = 6775 0062 self._prompt = "Horizons>" 0063 self._connection = None 0064 self._debug = False 0065 self._last_command_index = None 0066 0067 def connect(self): 0068 self._connection = telnetlib.Telnet(self._host, self._port) 0069 self._read_until_prompt() 0070 0071 def disconnect(self): 0072 self._connection.close() 0073 0074 def download_state_vectors(self, hspace_obj, t_srt=None, t_end=None): 0075 if self._connection is None: 0076 print("Not connected!") 0077 return 0078 print("Requesting data for {0} relative to {1}..." .format( 0079 hspace_obj.name, hspace_obj.related_body)) 0080 self._send(hspace_obj.horizons_id) 0081 0082 if t_srt is None: 0083 t_srt = hspace_obj.data_from 0084 if t_end is None or t_end <= t_srt: 0085 t_end = hspace_obj.data_until 0086 0087 body_id = self._body_ids[hspace_obj.related_body] 0088 0089 cmds = self._commands.keys() 0090 resp = [y.format(body=hspace_obj.related_body, 0091 body_id=body_id, 0092 datetimestart=self._unixtime_to_str(t_srt), 0093 datetimeend=self._unixtime_to_str(t_end), 0094 interval_days=hspace_obj.data_interval_days) 0095 for y in self._commands.values()] 0096 0097 done = False 0098 vectors = [] 0099 0100 while(not done): 0101 r, data = self._next_command(cmds, resp) 0102 if r == 'Done': 0103 done = True 0104 elif r == 'Data': 0105 print(" Found... Parsing...") 0106 vectors = self._parse_data(data) 0107 print(" Success") 0108 elif r == 'NoResponse': 0109 pass 0110 else: 0111 self._send(r) 0112 0113 return vectors 0114 0115 def _next_command(self, cmds, resp): 0116 idx, match, data = self._connection.expect(cmds, 120) 0117 if self._debug: 0118 print(data) 0119 if self._last_command_index == idx: 0120 print("Horizons repeated the last message:") 0121 print(data) 0122 print("Probably something went wrong. Aborting!") 0123 raise Exception() 0124 else: 0125 self._last_command_index = idx 0126 return (resp[idx], data) 0127 0128 def _unixtime_to_str(self, timestamp): 0129 return time.strftime('%Y-%m-%d %H:%M', time.gmtime(timestamp)) 0130 0131 def _jdate_to_unixtime(self, jstamp): 0132 return (jstamp - 2440587.5) * 86400 0133 0134 def _parse_data(self, data): 0135 sdat = data.split(',') 0136 if len(sdat) < 1: 0137 raise Exception() 0138 vectors = [] 0139 for i in range(0, len(sdat) - 1, 8): 0140 ldat = sdat[i:i+8] 0141 vectors.append( [ float(ldat[0]), 0142 float(ldat[2]), float(ldat[3]), float(ldat[4]), 0143 float(ldat[5]), float(ldat[6]), float(ldat[7]) ] ) 0144 return vectors 0145 0146 def _send(self, data): 0147 self._connection.write("{0}\n".format(data)) 0148 0149 def _read_until_prompt(self): 0150 self._connection.read_until(self._prompt) 0151 0152 0153 class TASCDownloader(object): 0154 0155 """A simple http client for ESA's TASC service at http://tasc.esa.int/.""" 0156 0157 _params = { 0158 'mission' : "{mission}", 0159 'querytyp' : "data", 0160 'qtyp' : "sta", 0161 'staobj' : "{object_name}", 0162 'starefobj' : "{related_body}", 0163 'staltc' : "NO", 0164 'stafrm' : "mean equatorial J2000", 0165 'tscl' : "TDB", 0166 'tstart' : "{t_start}", 0167 'tend' : "{t_end}", 0168 'tstp' : "{interval_days:03d} 00:00:00.000", 0169 'orbtyp' : "ops", 0170 } 0171 0172 def __init__(self): 0173 super(TASCDownloader, self).__init__() 0174 self._host = "tasc.esa.int" 0175 self._port = 80 0176 self._uri = '/cgi-bin/query.html' 0177 self._debug = False 0178 0179 def download_state_vectors(self, space_obj, t_start=None, t_end=None): 0180 print("Requesting data for {0} relative to {1}..." .format( 0181 space_obj.name, space_obj.related_body)) 0182 0183 if t_start is None: 0184 t_start = space_obj.data_from 0185 if t_end is None or t_end <= t_start: 0186 t_end = space_obj.data_until 0187 0188 start = time.strftime('%Y/%m/%d %H:%M:%S.000', time.gmtime(t_start)) 0189 end = time.strftime('%Y/%m/%d %H:%M:%S.000', time.gmtime(t_end)) 0190 interval_days = 30 0191 0192 params = [] 0193 for key in self._params.keys(): 0194 value = urllib.quote_plus(self._params[key].format( 0195 mission=space_obj.tasc_mission, 0196 object_name=space_obj.name, 0197 related_body=space_obj.related_body, 0198 t_start=start, 0199 t_end=end, 0200 interval_days=interval_days)) 0201 params = params + [ key, value ] 0202 0203 data = self._fetch_from_http(params) 0204 print(" Parsing...") 0205 return self._parse(data) 0206 0207 def _fetch_from_http(self, params): 0208 con = httplib.HTTPConnection(self._host, self._port) 0209 p = [] 0210 for i in range(0, len(params), 2): 0211 p = p + [ params[i] + "=" + params[i+1] ] 0212 uri = self._uri + '?' + '&'.join(p) 0213 if self._debug: 0214 print("Request:", uri) 0215 con.request('GET', uri) 0216 response = con.getresponse() 0217 if response.status != 200: 0218 print("Failed to load data via HTTP:", 0219 response.status, response.reason) 0220 return "" 0221 return response.read() 0222 0223 def _parse(self, data): 0224 if self._debug: 0225 print(data) 0226 0227 dl = [x for x in data.split("\n") 0228 if x != "" and x.find("#") == -1 and x.find("<") == -1] 0229 0230 if len(dl) < 1: 0231 return [] 0232 0233 vecs = [] 0234 for line in dl: 0235 fields = line.split() 0236 if len(fields) != 7: 0237 print("Invalid data line:", line) 0238 continue; 0239 vec = [ self._TDB_to_JD(fields[0]), 0240 float(fields[1]), float(fields[2]), float(fields[3]), 0241 float(fields[4]), float(fields[5]), float(fields[6])] 0242 vecs.append(vec) 0243 return vecs 0244 0245 def _TDB_to_JD(self, tdb): 0246 tdb = tdb.split(".")[0] # strip .000 0247 t = calendar.timegm(time.strptime(tdb, '%Y/%m/%dT%H:%M:%S')) 0248 return t / 86400 + 2440587.5 0249 0250 0251 class DataDownloader(object): 0252 0253 def __init__(self): 0254 super(DataDownloader, self).__init__() 0255 self._horizons = HorizonsDownloader() 0256 self._horizons.connect() 0257 self._tasc = TASCDownloader() 0258 0259 def __del__(self): 0260 self._horizons.disconnect() 0261 0262 def download_state_vectors(self, space_obj): 0263 if isinstance(space_obj, HorizonsSpaceObject): 0264 return self._horizons.download_state_vectors(space_obj) 0265 elif isinstance(space_obj, TASCSpaceObject): 0266 return self._tasc.download_state_vectors(space_obj) 0267 else: 0268 raise NotImplementedError(space_obj.__class__.__name__)