def __init__(self) : self.emission_altitude = 0.0 self.time_offset = 0.0 self.iref = 'J2000' self.abcorr = 'LT' self.data = {} self.constants() self.sc_id = spice.bodn2c(self.spacecraft) self.target_id = spice.bodn2c(self.target) self.framestring = 'IAU_' + self.target self.set_emission_altitude(self.emission_altitude) # The angle from the centre of the pixel to the edge self.xpxoffset = 0.5 * self.pxscale / 1000.0 self.ypxoffset = 0.5 * self.pyscale / 1000.0 # Generate a key mapping, so that we know which index is what. self.keys = ['lat', 'lon', 'limb_lat', 'limb_lon', 'lat_graphic', 'lon_graphic','phase', 'emission', 'incidence', 'localtime', 'losdist', 'limb_distance', 'bodyintercept'] self.map = {} for key, value in enumerate(self.keys) : self.map[value] = key self.angles = ['lat', 'lon', 'limb_lat', 'limb_lon', 'lat_graphic', 'lon_graphic','phase', 'emission', 'incidence']
def computeDataFrameGeometries(self, data_frames, greg_format_string, julian_format_string): for data_frame in data_frames: self.greg_format_string = greg_format_string self.julian_format_string = julian_format_string greg_split_string = self.greg_format_string.split(' ') data_frame.greg_time_system_string = [ i for i in greg_split_string if '::' in i ][0][2:] julian_split_string = self.julian_format_string.split(' ') data_frame.julian_time_system_string = [ i for i in julian_split_string if '::' in i ][0][2:] current_epoch = data_frame.start_epoch current_epoch = spice.str2et(current_epoch) stop_epoch = spice.str2et(data_frame.stop_epoch) time_remaining = stop_epoch - current_epoch while time_remaining >= 0.0: greg_date_string = spice.timout(current_epoch, self.greg_format_string) JD_date = spice.timout(current_epoch, self.julian_format_string) for body_pair in data_frame.body_pairs: # get target body state body_state, light_times = spice.spkez( spice.bodn2c(body_pair[1]), current_epoch, data_frame.frame, 'NONE', spice.bodn2c(body_pair[0])) distance_km = self.np.linalg.norm(body_state[0:3]) distance_AU = distance_km / self.AU data_frame.data[body_pair[0]][body_pair[1]].append([ greg_date_string, JD_date, body_state, distance_km, distance_AU ]) # compute solar geometries SEP_angle, SPE_angle = self.computeSEPandSPEangles( current_epoch, data_frame.spacecraft_SPICE_ID) data_frame.data['SEP'].append( [greg_date_string, JD_date, SEP_angle]) data_frame.data['SPE'].append( [greg_date_string, JD_date, SPE_angle]) # compute any eclipses self.computeCircleCircleOccultations(current_epoch, greg_date_string, JD_date, data_frame) if time_remaining == 0.0: break else: if time_remaining >= data_frame.time_step: current_epoch += data_frame.time_step time_remaining -= data_frame.time_step else: time_remaining -= stop_epoch - current_epoch current_epoch += stop_epoch - current_epoch
def __init__(self, **kwargs): from spiceypy.utils.support_types import SpiceyError super(PSP, self).__init__("psp", body_name="SPP", **kwargs) try: spiceypy.bodn2c("SPP_SPACECRAFT") except SpiceyError: # kernel fix that is required due to PSP having different names spiceypy.boddef("SPP_SPACECRAFT", spiceypy.bodn2c("SPP"))
def naifID(bodyStr): try: naifID = spice.bodn2c(bodyStr.upper()) return naifID except spice.stypes.SpiceyError as e: print("ERROR: INVALID SPICE NAIF NAME ENTERED") raise e
def test_eros_spk2_coverage(self): code = spice.bodn2c('EROS') cover = spice.stypes.SPICEDOUBLE_CELL(1000) spice.spkcov(self.near.SpkEros2, code, cover) result = [x for x in cover] expected_result = [-31334400.0, 81432000.0] np.testing.assert_array_almost_equal(result, expected_result)
def test_near_spk_orbit_coverage(self): code = spice.bodn2c('NEAR') cover = spice.stypes.SPICEDOUBLE_CELL(1000) spice.spkcov(self.near.SpkNearOrbit, code, cover) result = [x for x in cover] expected_result = [-43200.0, 35279120.0] np.testing.assert_array_almost_equal(result, expected_result)
def srfrec(self, surfcoord, body=None): """Convert lon/lat to rectangular coordinates. Convert planetocentric longitude and latitude of a surface point on a specified body to rectangular coordinates. self.target needs to be set for this! Parameters ---------- surfcoord : SurfaceCoords Instance of SurfaceCoords. body : str or int, optional SPICE body str (will be converted) or SPICE body id. Default: self.body Examples -------- >>> mspice = MarsSpicer() >>> print('{0:g} {1:g} {2:g}'.format(*mspice.srfrec(0,85))) 294.268 0 3363.5 """ if body is None: body = self.target_id if not str(body).isdigit(): body = spice.bodn2c(body) return spice.srfrec(body, surfcoord.lon.value, surfcoord.lat.value)
def __init__(self, sensor, host=object(), target=object(), time=object()): if isinstance(sensor, str): name = sensor id = cspice.bodn2c(sensor) else: id = sensor name = cspice.bodc2n(sensor) room = 99 shapelen = 1000 framelen = 1000 fov_shape, frame, bsight, n, fov_bounds = cspice.getfov( id, room, shapelen, framelen) self.host = host self.target = target self.time = time self.name = name self.id = id self.fov_shape = fov_shape self.frame = frame self.bsight_ins = bsight self.fov_bounds = fov_bounds self.bsight_obs = [] self.spoint = []
def test_eros_spk1_coverage(self): spice.furnsh(self.near.metakernel) code = spice.bodn2c('EROS') cover = spice.stypes.SPICEDOUBLE_CELL(1000) spice.spkcov(self.near.SpkEros, code, cover) result = [x for x in cover] expected_result = [-126273600.0, 37886400.0] np.testing.assert_array_almost_equal(result, expected_result) spice.kclear()
def reference_frame(self): """ Returns a string containing the name of the target reference frame Expects target_name to be defined. This must be a string containing the name of the target body Returns ------- : str String name of the target reference frame """ try: return spice.cidfrm(spice.bodn2c(self.target_name))[1] except: return 'IAU_{}'.format(self.target_name)
def definition(sensor): # # We check if the resolution of the camera has been provided as an input # if not we try to obtain the resolution of the camera from the IK # sensor_id = spiceypy.bodn2c(sensor) pixel_samples = spiceypy.gdpool(f'INS{sensor_id}_PIXEL_SAMPLES', 0, 1)[0] pixel_lines = spiceypy.gdpool(f'INS{sensor_id}_PIXEL_LINES', 0, 1)[0] ccd_center = spiceypy.gdpool(f'INS{sensor_id}_CCD_CENTER', 0, 2) print(f'{sensor} pixel_samples (x) = {pixel_samples}') print(f'{sensor} pixel_lines (y) = {pixel_lines}') print(f'{sensor} ccd_sensor (x,y) = {ccd_center[0]},{ccd_center[1]}') return
def srfrec(self, lon, lat, body=None): """Convert lon/lat to rectangular coordinates. Convert planetocentric longitude and latitude of a surface point on a specified body to rectangular coordinates. Input of angles in degrees, conversion is done here. If the body is not a SPICE ID, it will be converted. >>> mspice = MarsSpicer() >>> print('{0:g} {1:g} {2:g}'.format(*mspice.srfrec(0,85))) 294.268 0 3363.5 """ if body is None: body = self.target_id if not str(body).isdigit(): body = spice.bodn2c(body) return spice.srfrec(body, np.deg2rad(lon), np.deg2rad(lat))
def calc_utc_from_sclk(coarse, fine): """Requires to have SPICE kernels loaded (use load_kernels()). I'm not doing the loading of SPICE kernels in here so that I can run this function in a loop without using up kernel space. The caller should load and unload kernels as required. """ # load_kernels() # load SPICE kernels timestamp_ = coarse ss = fine mavenid, _ = spice.bodn2c('MAVEN') timestamp = timestamp_ + ss / 65536 # timestamp_ and ss are already float sec = np.uint64(timestamp) subsec = np.uint64((timestamp - sec) * 65536) sclkch = np.vectorize(format_times)(sec, subsec) sclkdp = np.vectorize(spice.scencd)(mavenid, sclkch) et = np.vectorize(spice.sct2e)(mavenid, sclkdp) utc = np.vectorize(spice.et2utc)(et, 'ISOC', 50, 100) return pd.to_datetime(utc)
def __init__(self, body, time=object(), target=None): if isinstance(body, str): name = body id = spiceypy.bodn2c(body) else: id = body name = spiceypy.bodc2n(body) if target: self.target = target self.name = name self.id = id self.time = time # # Parameters for the Geometry Computation # self.previous_tw = [] self.geometry_flag = False
def __init__(self, ut, kernel_path, target='JUPITER'): ''' `Projector` class initializer Parameters ---------- ut : string Observation time in the UTC ISO format: yyyy-mm-dd hh:mm:ss kernel_path : string Path to the NAIF generic_kernels folder target : string, [default=Jupiter] Name of the observing body ''' ## find and load the kernels find_spice_kernels(kernel_path) ## convert the times self.ut = ut self.et = spiceypy.utc2et(ut) ## calculate target information self.target=target self.target_frame = 'IAU_'+target self.radii = spiceypy.bodvar(spiceypy.bodn2c(target), "RADII", 3) self.flattening = (self.radii[0] - self.radii[2])/self.radii[0] ## get the target state in J2000 self.tstate, self.lt = \ spiceypy.spkezr(target, self.et, 'J2000', 'CN', "EARTH") self.tRA, self.tDEC = \ spiceypy.recrad(self.tstate[:3])[1:] self.find_sub_pt() self.find_limb() self.platecal = PlateCalibration(self.RADec0, self.RArotang)
def pixel_boresight(sensor, i, j): # # We check if the resolution of the camera has been provided as an input # if not we try to obtain the resolution of the camera from the IK # sensor_id = spiceypy.bodn2c(sensor) (shape, sensor_frame, bsight, vectors, bounds) = spiceypy.getfov(sensor_id, 100) pixel_samples = spiceypy.gdpool(f'INS{sensor_id}_PIXEL_SAMPLES', 0, 1) pixel_lines = spiceypy.gdpool(f'INS{sensor_id}_PIXEL_LINES', 0, 1) # # We generate a matrix using the resolution of the framing camera as the # dimensions of the matrix # nx, ny = (int(pixel_lines[0]), int(pixel_samples[0])) x = np.linspace(bounds[0][0], bounds[2][0], nx) y = np.linspace(bounds[0][1], bounds[2][1], ny) return [x[int(i)], y[int(j)], bsight[2]]
def __init__(self, inst, et, abcorr, obsrvr, width, height, mag_limit): # input parameter self.inst = inst self.et = et self.abcorr = abcorr self.obsrvr = obsrvr self.width = width self.height = height # parameters equivalent to input parameter self.date = spice.et2utc(et, "ISOC", 3) self.inst_id = spice.bodn2c(inst) # Instrument FOV self.fov = Fov(self.inst_id) self.fov_in_degrees = self.fov.fovmax * 2.0 * spice.dpr() # geometry information self.pos, _ = spice.spkpos(obsrvr, et, self.fov.frame, abcorr, "SUN") self.obs2refmtx = spice.pxform(self.fov.frame, "J2000", et) self.ref2obsmtx = spice.pxform("J2000", self.fov.frame, et) # screen information pos_angle, angle_res, ra, dec = get_geometry_info( self.obs2refmtx, self.fov, width, height ) self.center = self.fov.bounds_rect.center_vec self.pos_angle = pos_angle self.angle_res = angle_res self.ra = ra self.dec = dec # searched objects self.solar_objects = search_solar_objects(self) self.stars = search_stars(self, mag_limit)
def getTargetPosition(target, craft, camera, time): """ get position of target in world coordinates relative to the observing craft. """ # get target code targetId = spice.bodn2c(target) # eg 'Jupiter'->599 # get spacecraft instrument spacecraft = -31 if craft=='Voyager1' else -32 spacecraftBus = spacecraft * 1000 spacecraftScanPlatform = spacecraftBus - 100 spacecraftNarrowCamera = spacecraftScanPlatform - 1 spacecraftWideCamera = spacecraftScanPlatform - 2 # instrument = spacecraftBus # use for NAIF continuous kernels instrument = spacecraftScanPlatform # use for PDS discrete kernels # get ephemeris time # note: target and spacecraft locations are stored relative to J2000 # time is utc time as string ephemerisTime = spice.str2et(time) # seconds since J2000 (will be negative) # sclkch = spice.sce2s(spacecraft, ephemerisTime) # spacecraft clock ticks, string # sclkdp = spice.sce2c(spacecraft, ephemerisTime) # spacecraft clock ticks, double clockTicks = spice.sce2c(spacecraft, ephemerisTime) # spacecraft clock ticks, double # print 'clockTicks',clockTicks # get position of target relative to spacecraft # this is the direction from craft to target in ECLIPB1950 frame observer = 'Voyager ' + craft[-1] # eg 'Voyager 1' frame = 'ECLIPB1950' # coordinate frame abberationCorrection = 'NONE' position, lightTime = spice.spkpos(target, ephemerisTime, frame, abberationCorrection, observer) # print 'target position relative to observer', position return position
def target2frame(target): if target == '67P/C-G': target_frame = '67P/C-G_CK' elif target == '21 LUTETIA': target_frame = 'LUTETIA_FIXED' elif target == 'DIDYMOS' or target == 'DIDYMOON': target_frame = '{}_FIXED'.format(target) else: try: target_frame = 'IAU_' + target.upper() target_frame_id = spiceypy.namfrm(target_frame) if target_frame_id == 0: raise Exception except: try: target_id = str(spiceypy.bodn2c(target)) target_frame = spiceypy.frmnam(int(target_id)) except: target_id += '000' target_frame = spiceypy.frmnam(int(target_id)) return target_frame
def __init__(self, kernels_folder=None): """ Parameters: kernels_folder (string): If not provided, the path stored in the environment variable ``TESSPHOT_SPICE_KERNELS`` is used, and if that is not set, the ``data/spice`` directory is used. """ logger = logging.getLogger(__name__) # If no kernel folder is given, used the one stored in env.var. or the default location: if kernels_folder is None: kernels_folder = os.environ.get( 'TESSPHOT_SPICE_KERNELS', os.path.join(os.path.dirname(__file__), 'data', 'spice')) # Create list of kernels that should be loaded: files = ( # Planetary ephemeris and TESS clock kernels: 'tess2018338154046-41240_naif0012.tls', 'tess2018338154429-41241_de430.bsp', 'tess2019113195500-41374_sclk.tsc', # Predictive kernels of TESS's expected position: #'TESS_EPH_PRE_2YEAR_2018171_01.bsp', 'TESS_EPH_PRE_LONG_2018109_02.bsp', 'TESS_EPH_PRE_LONG_2019045_01.bsp', # Definite kernels of TESS's actual position: #'TESS_EPH_DEF_2018004_01.bsp', # Does not contain any information 'TESS_EPH_DEF_2018080_01.bsp', #'TESS_EPH_DEF_2018108_01.bsp', # Surpassed by never version below 'TESS_EPH_DEF_2018108_02.bsp', 'TESS_EPH_DEF_2018115_01.bsp', 'TESS_EPH_DEF_2018124_01.bsp', 'TESS_EPH_DEF_2018133_01.bsp', 'TESS_EPH_DEF_2018150_01.bsp', 'TESS_EPH_DEF_2018183_01.bsp', 'TESS_EPH_DEF_2018186_01.bsp', 'TESS_EPH_DEF_2018190_01.bsp', 'TESS_EPH_DEF_2018193_01.bsp', 'TESS_EPH_DEF_2018197_01.bsp', 'TESS_EPH_DEF_2018200_01.bsp', 'TESS_EPH_DEF_2018204_01.bsp', 'TESS_EPH_DEF_2018207_01.bsp', 'TESS_EPH_DEF_2018211_01.bsp', 'TESS_EPH_DEF_2018214_01.bsp', 'TESS_EPH_DEF_2018218_01.bsp', 'TESS_EPH_DEF_2018221_01.bsp', 'TESS_EPH_DEF_2018225_01.bsp', 'TESS_EPH_DEF_2018228_01.bsp', 'TESS_EPH_DEF_2018232_01.bsp', 'TESS_EPH_DEF_2018235_01.bsp', 'TESS_EPH_DEF_2018239_01.bsp', 'TESS_EPH_DEF_2018242_01.bsp', 'TESS_EPH_DEF_2018246_01.bsp', 'TESS_EPH_DEF_2018249_01.bsp', 'TESS_EPH_DEF_2018253_01.bsp', 'TESS_EPH_DEF_2018256_01.bsp', 'TESS_EPH_DEF_2018260_01.bsp', 'TESS_EPH_DEF_2018263_01.bsp', 'TESS_EPH_DEF_2018268_01.bsp', 'TESS_EPH_DEF_2018270_01.bsp', 'TESS_EPH_DEF_2018274_01.bsp', 'TESS_EPH_DEF_2018277_01.bsp', 'TESS_EPH_DEF_2018282_01.bsp', 'TESS_EPH_DEF_2018285_01.bsp', 'TESS_EPH_DEF_2018288_01.bsp', 'TESS_EPH_DEF_2018291_01.bsp', 'TESS_EPH_DEF_2018295_01.bsp', 'TESS_EPH_DEF_2018298_01.bsp', 'TESS_EPH_DEF_2018302_01.bsp', 'TESS_EPH_DEF_2018305_01.bsp', 'TESS_EPH_DEF_2018309_01.bsp', 'TESS_EPH_DEF_2018312_01.bsp', 'TESS_EPH_DEF_2018316_01.bsp', 'TESS_EPH_DEF_2018319_01.bsp', 'TESS_EPH_DEF_2018323_01.bsp', 'TESS_EPH_DEF_2018327_01.bsp', 'TESS_EPH_DEF_2018330_01.bsp', 'TESS_EPH_DEF_2018333_01.bsp', 'TESS_EPH_DEF_2018337_01.bsp', 'TESS_EPH_DEF_2018340_01.bsp', 'TESS_EPH_DEF_2018344_01.bsp', 'TESS_EPH_DEF_2018347_01.bsp', 'TESS_EPH_DEF_2018351_01.bsp', 'TESS_EPH_DEF_2018354_01.bsp', 'TESS_EPH_DEF_2018358_01.bsp', 'TESS_EPH_DEF_2018361_01.bsp', 'TESS_EPH_DEF_2018365_01.bsp', 'TESS_EPH_DEF_2019003_01.bsp', 'TESS_EPH_DEF_2019007_01.bsp', 'TESS_EPH_DEF_2019010_01.bsp', 'TESS_EPH_DEF_2019014_01.bsp', 'TESS_EPH_DEF_2019017_01.bsp', 'TESS_EPH_DEF_2019021_01.bsp', 'TESS_EPH_DEF_2019024_01.bsp', 'TESS_EPH_DEF_2019028_01.bsp', 'TESS_EPH_DEF_2019031_01.bsp', 'TESS_EPH_DEF_2019035_01.bsp', 'TESS_EPH_DEF_2019038_01.bsp', 'TESS_EPH_DEF_2019042_01.bsp', 'TESS_EPH_DEF_2019045_01.bsp', 'TESS_EPH_DEF_2019049_01.bsp', 'TESS_EPH_DEF_2019052_01.bsp', 'TESS_EPH_DEF_2019056_01.bsp', 'TESS_EPH_DEF_2019059_01.bsp', 'TESS_EPH_DEF_2019063_01.bsp', 'TESS_EPH_DEF_2019066_01.bsp', 'TESS_EPH_DEF_2019070_01.bsp', 'TESS_EPH_DEF_2019073_01.bsp', 'TESS_EPH_DEF_2019077_01.bsp', 'TESS_EPH_DEF_2019080_01.bsp', 'TESS_EPH_DEF_2019084_01.bsp', 'TESS_EPH_DEF_2019087_01.bsp', 'TESS_EPH_DEF_2019091_01.bsp', 'TESS_EPH_DEF_2019094_01.bsp', 'TESS_EPH_DEF_2019098_01.bsp', 'TESS_EPH_DEF_2019102_01.bsp', 'TESS_EPH_DEF_2019105_01.bsp', 'TESS_EPH_DEF_2019108_01.bsp', 'TESS_EPH_DEF_2019112_01.bsp', 'TESS_EPH_DEF_2019115_01.bsp', 'TESS_EPH_DEF_2019119_01.bsp', 'TESS_EPH_DEF_2019122_01.bsp', 'TESS_EPH_DEF_2019126_01.bsp') # Make sure the kernel directory exists: if not os.path.exists(kernels_folder): os.makedirs(kernels_folder) # Automatically download kernels from TASOC, if they don't already exist? #urlbase = 'https://archive.stsci.edu/missions/tess/models/' urlbase = 'https://tasoc.dk/pipeline/spice/' for fname in files: fpath = os.path.join(kernels_folder, fname) if not os.path.exists(fpath): download_file(urlbase + fname, fpath) # Path where meta-kernel will be saved: fileshash = hashlib.md5(','.join(files).encode()).hexdigest() self.METAKERNEL = os.path.abspath( os.path.join(kernels_folder, 'metakernel-' + fileshash + '.txt')) # Write meta-kernel to file: if not os.path.exists(self.METAKERNEL): with open(self.METAKERNEL, 'w') as fid: fid.write("KPL/MK\n") fid.write(r"\begindata" + "\n") fid.write("PATH_VALUES = ('" + os.path.abspath(kernels_folder) + "')\n") fid.write("PATH_SYMBOLS = ('KERNELS')\n") fid.write("KERNELS_TO_LOAD = (\n") fid.write(",\n".join( ["'$KERNELS/" + fname + "'" for fname in files])) fid.write(")\n") fid.write(r"\begintext" + "\n") fid.write("End of MK file.\n") # Because SpiceyPy loads kernels into a global memory scope (BAAAAADDDD SpiceyPy!!!), # we first check if we have already loaded this into the global scope: # This is to attempt to avoid loading in the same kernels again and again when # running things in parallel. already_loaded = False for k in range(spiceypy.ktotal('META')): if os.path.abspath(spiceypy.kdata(k, 'META')[0]) == self.METAKERNEL: logger.debug("SPICE Meta-kernel already loaded.") already_loaded = True break # Define TESS object if it doesn't already exist: try: spiceypy.bodn2c('TESS') except SpiceyError: logger.debug("Defining TESS name in SPICE") spiceypy.boddef('TESS', -95) # Load kernels if needed: if not already_loaded: logger.debug("Loading SPICE Meta-kernel: %s", self.METAKERNEL) spiceypy.furnsh(self.METAKERNEL) # Let's make sure astropy is using the de430 kernels as well: # Default is to use the same as is being used by SPOC (de430). # If using astropy 4.0+, we can load the local one directly. Before this, # it needs to be downloaded and cached: # NOTE: https://github.com/astropy/astropy/pull/8767 #self.planetary_ephemeris = 'de430' if astropy_major_version >= 4: self.planetary_ephemeris = os.path.abspath( os.path.join(kernels_folder, 'tess2018338154429-41241_de430.bsp')) else: self.planetary_ephemeris = 'https://tasoc.dk/pipeline/spice/tess2018338154429-41241_de430.bsp' self._old_solar_system_ephemeris = coord.solar_system_ephemeris.get() coord.solar_system_ephemeris.set(self.planetary_ephemeris)
def geometry(et, bsight, target, frame, sensor, observer=''): if not observer: observer = sensor # Time tag [UTC] # pixel id [(x,y)] # corner id [(x,y)] # Requested geometry # lat lon intersection (planetocentric) # lat lon subspacecraft # lat lon subsolar # target distance intersection # target angular diameter # local solar time intersection # phase angle intersection # emission angle intersection # incidence angle intersection # # We retrieve the camera information using GETFOV. More info available: # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/getfov_c.html # sensor_id = spiceypy.bodn2c(sensor) (shape, sensor_frame, ibsight, vectors, bounds) = spiceypy.getfov(sensor_id, 100) visible = spiceypy.fovtrg(sensor, target, 'ELLIPSOID', frame, 'LT+S', observer, et) if not visible: return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 tarid = spiceypy.bodn2c(target) n, radii = spiceypy.bodvrd(target, 'RADII', 3) re = radii[0] rp = radii[2] f = (re - rp) / re try: # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sincpt_c.html # # For each pixel we compute the possible intersection with the target, if # the target is intersected we then compute the illumination angles. We # use the following SPICE APIs: SINCPT and ILLUMF # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sincpt_c.html # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html # (spoint, trgepc, srfvec) = \ spiceypy.sincpt('ELLIPSOID', target, et, frame, 'LT+S', observer, sensor_frame, bsight) (tarlon, tarlat, taralt) = spiceypy.recgeo(spoint, re, f) tardis = spiceypy.vnorm(srfvec) # # Angular diameter # tarang = np.degrees( 2 * np.arctan(max(radii) / spiceypy.vnorm(spoint + srfvec))) # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html # (trgenpc, srfvec, phase, incdnc, emissn, visiblef, iluminatedf) = \ spiceypy.illumf('ELLIPSOID', target, 'SUN', et, frame, 'LT+S', observer, spoint) phase *= spiceypy.dpr() incdnc *= spiceypy.dpr() emissn *= spiceypy.dpr() # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/et2lst_c.html # # VARIABLE I/O DESCRIPTION # -------- --- -------------------------------------------------- # et I Epoch in seconds past J2000 epoch. # body I ID-code of the body of interest. # lon I Longitude of surface point (RADIANS). # type I Type of longitude "PLANETOCENTRIC", etc. # timlen I Available room in output time string. # ampmlen I Available room in output `ampm' string. # hr O Local hour on a "24 hour" clock. # mn O Minutes past the hour. # sc O Seconds past the minute. # time O String giving local time on 24 hour clock. # ampm O String giving time on A.M./ P.M. scale. (hr, mn, sc, ltime, ampm) = \ spiceypy.et2lst(et, tarid, tarlon, 'PLANETOCENTRIC', 80, 80) # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/subpnt_c.html # # Variable I/O Description # -------- --- -------------------------------------------------- # method I Computation method. # target I Name of target body. # et I Epoch in TDB seconds past J2000 TDB. # fixref I Body-fixed, body-centered target body frame. # abcorr I Aberration correction flag. # obsrvr I Name of observing body. # spoint O Sub-observer point on the target body. # trgepc O Sub-observer point epoch. # srfvec O Vector from observer to sub-observer point # (spoint, trgepc, srfev) = \ spiceypy.subpnt('INTERCEPT/ELLIPSOID', target, et, frame, 'LT+S', observer) (sublon, sublat, subalt) = spiceypy.recgeo(spoint, re, f) # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/subslr_c.html # # Variable I/O Description # -------- --- -------------------------------------------------- # method I Computation method. # target I Name of target body. # et I Epoch in ephemeris seconds past J2000 TDB. # fixref I Body-fixed, body-centered target body frame. # abcorr I Aberration correction. # obsrvr I Name of observing body. # spoint O Sub-solar point on the target body. # trgepc O Sub-solar point epoch. # srfvec O Vector from observer to sub-solar point. # (spoint, trgepc, srfev) = \ spiceypy.subslr('INTERCEPT/ELLIPSOID', target, et, frame, 'LT+S', observer) (sunlon, sunlat, sunalt) = spiceypy.recgeo(spoint, re, f) return tarlon, tarlat, sublon, sublat, sunlon, sunlat, tardis, tarang, ltime, phase, emissn, incdnc except: return 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
def _get_target_id(self): return spice.bodn2c(self.target)
90 - 90, # Inclination 0.24, # Diameter (1AU) -1, # Handedness 12, # Strength (1AU) 10, # Turns 1.5e-7, # Drag parameter 400, # Solar wind speed 5 # Solar wind strength ) # load spice kernels mfr3dcore.spice.load_kernels("generic") mfr3dcore.spice.load_kernels("SPP") # required because of two separate names for PSP spiceypy.boddef("SPP_SPACECRAFT", spiceypy.bodn2c("SPP")) # crossing times (hours after t0, these values were found manually) t1 = t0 + datetime.timedelta(hours=3) t2 = t0 + datetime.timedelta(hours=28) # ========== 3D PLOTS ========== # define time series for simulation ts = [ t0 + datetime.timedelta(minutes=i) for i in range(-1 * 60, 50 * 60, 5) ] #ts_step1 = [t0 + datetime.timedelta(hours=i) for i in range(-1, 1 + 1)] #ts_step2 = [t1 + datetime.timedelta(hours=i) for i in range(-1, 1 + 1)] #ts_step3 = [t2 + datetime.timedelta(hours=i) for i in range(-1, 1 + 1)]
def simulate_image(utc, metakernel, camera, target, target_frame, pixel_lines=False, pixel_samples=False, dsk=False, generate_image=False, plot_image=False, report=False, name=False): ''' :param utc: Image acquisition time in UTC format e.g.: 2016-01-01T00:00:00 :type utc: str :param metakernel: SPICE Kernel Dataset Meta-Kernel :type metakernel: str :param camera: Name of the camera to be used. Usually found in the instrument kernel (IK) e.g.: 'ROS_NAVCAM-A' :type camera: str :param target: Target of the observation, e.g.:'67P/C-G' :type target: str :param target_frame: SPICE reference frame of the target. Usually found in the frames kernel (FK) :type target_frame: str :param pixel_lines: Number of pixel lines usually provided by the IK. :type pixel_lines: int :param pixel_samples: Number of pixel samples per line usually provided by the IK. :type pixel_samples: int :param dsk: Digital Shape Model to be used for the computation. Not required of included in the Meta-Kernel. :type dsk: str :param generate_image: Flag to determine whether if the image is saved or plotted. :type generate_image: bool :param plot_image: Flag to determine whether if the image is to be plotted or plotted. :type generate_image: bool :param report: Flag for processing report. :type generate_image: bool :param name: Name to be provided to the image :type generate_image: str :return: Name of the output image :rtype: str ''' spiceypy.furnsh(metakernel) if dsk: spiceypy.furnsh(dsk) method = 'DSK/UNPRIORITIZED' else: method = 'ELLIPSOID' et = spiceypy.utc2et(utc) # # We retrieve the camera information using GETFOV. More info available: # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/getfov_c.html # camera_name = camera camera_id = spiceypy.bodn2c(camera_name) (shape, frame, bsight, vectors, bounds) = spiceypy.getfov(camera_id, 100) # # TODO: In the future all the sensors should be epehmeris objects, see # https://issues.cosmos.esa.int/socci/browse/SPICEMNGT-77 # if camera.split('_')[0] == 'ROS': observer = 'ROSETTA' elif camera.split('_')[0] == 'MEX': observer = 'MEX' elif camera.split('_')[0] == 'VEX': observer = 'VEX' elif camera.split('_')[0] == 'JUICE': observer = 'JUICE' else: observer = camera # # We check if the resolution of the camera has been provided as an input # if not we try to obtain the resolution of the camera from the IK # if not pixel_lines or not pixel_samples: try: pixel_samples = int( spiceypy.gdpool('INS' + str(camera_id) + '_PIXEL_SAMPLES', 0, 1)) pixel_lines = int( spiceypy.gdpool('INS' + str(camera_id) + '_PIXEL_LINES', 0, 1)) except: pass print("PIXEL_SAMPLES and/or PIXEL_LINES not defined for " "{}".format(camera)) return # # We generate a matrix using the resolution of the framing camera as the # dimensions of the matrix # nx, ny = (pixel_samples, pixel_lines) x = np.linspace(bounds[0][0], bounds[2][0], nx) y = np.linspace(bounds[0][1], bounds[2][1], ny) xv, yv = np.meshgrid(x, y) # # We define the matrices that will be used as outputs and the # phase_matrix = np.zeros((nx, ny)) emissn_matrix = np.zeros((nx, ny)) solar_matrix = np.zeros((nx, ny)) # # For each pixel we compute the possible intersection with the target, if # the target is intersected we then compute the illumination angles. We # use the following SPICE APIs: SINCPT and ILLUMF # # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/sincpt_c.html # https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/C/cspice/illumf_c.html # isvisible, isiluminated = [], [] for i, x in enumerate(xv): for j, y in enumerate(yv): # # List of pixel's boresight # ibsight = [x[i], y[j], bsight[2]] try: (spoint, trgepc, srfvec) = spiceypy.sincpt(method, target, et, target_frame, 'NONE', observer, frame, ibsight) (trgepc, srfvec, phase, solar, emissn, visiblef, iluminatedf) = spiceypy.illumf(method, target, 'SUN', et, target_frame, 'LT+S', observer, spoint) emissn_matrix[i, j] = emissn phase_matrix[i, j] = phase # # Add to list if the point is visible to the camera # if visiblef == True: isvisible.append(visiblef) # # Add to list if the point is illuminated and seen by the camera # if iluminatedf == True: isiluminated.append(iluminatedf) solar_matrix[i, j] = solar else: # # And we set the not illuminated pixels with np.pi/2 # solar_matrix[i, j] = np.pi / 2 except: pass # # If SINCPT raises an error, we set that we see nothing in # the pixel. # emissn_matrix[i, j] = 0 phase_matrix[i, j] = np.pi solar_matrix[i, j] = np.pi / 2 if report: print('Pixel report for {} w.r.t {} @ {}'.format(camera, target, utc)) print(' Total number of pixels: ', pixel_samples * pixel_lines) print(' Illuminated pixels: ', len(isvisible)) print(' Hidden pixels: ', pixel_samples * pixel_lines - len(isvisible)) print(' Shadowed points: ', pixel_samples * pixel_lines - len(isiluminated)) # # We transform the matrix from illumination angles to greyscale [0-255] # rescaled = (255 / (solar_matrix.max() - solar_matrix.min()) * (solar_matrix - solar_matrix.min())).astype(np.uint8) rescaled = -np.flip(rescaled, 0) + 255 # # We generate the plot # if generate_image: if not name: name = '{}_{}_{}.png'.format(camera.lower(), name, utc.lower()) imageio.imwrite(name, rescaled) if plot_image: plt.imshow(rescaled, cmap='gray') plt.axis('off') plt.show() return name
utctime_start = sp.str2et(utcstring_start) utctime_end = sp.str2et(utcstring_end) total_seconds = utctime_end - utctime_start nsteps = int(np.floor(total_seconds / step)) times = np.arange(nsteps) * step + utctime_start time_strings = [ sp.et2utc(time_in, formatstr, prec) for time_in in list(times) ] tgo_pos = np.asfarray([ sp.spkpos(target, time, ref, abcorr, observer)[0] for time in list(times) ]) tgo_dist = la.norm(tgo_pos, axis=1) code = sp.bodn2c(target) pradii = sp.bodvcd(code, 'RADII', 3) # 10 = Sun sun_radius = pradii[1][0] sun_diameter_arcmins = np.arctan( sun_radius / tgo_dist) * sp.dpr() * 60.0 * 2.0 fig1 = plt.figure(figsize=(figx - 6, figy - 5)) plt.plot(sun_diameter_arcmins) plt.xlabel("UTC time") plt.ylabel("Solar diameter as seen from TGO (arcminutes)") title = "Apparent diameter of Sun over 1 Martian year" plt.title(title) x_tick_indices = range(0, len(times), 24 * 183) x_tick_names = [ time_strings[x_tick_index] for x_tick_index in x_tick_indices ]
def _get_instrument_id(self): if not self.instrument: print("Instrument is not set yet.") return return spice.bodn2c(self.instrument)
def pixel_samples(sensor): sensor_id = spiceypy.bodn2c(sensor) return spiceypy.gdpool(f'INS{sensor_id}_PIXEL_SAMPLES', 0, 1)[0]
def pixel_lines(sensor): sensor_id = spiceypy.bodn2c(sensor) return spiceypy.gdpool(f'INS{sensor_id}_PIXEL_LINES', 0, 1)[0]
def ccd_center(sensor): sensor_id = spiceypy.bodn2c(sensor) return spiceypy.gdpool(f'INS{sensor_id}_CCD_CENTER', 0, 2)
def vgMap(filterVolumes=None, optionOverwrite=False, directCall=True): "Build up 2d color map" # # cmd = "echo $ISISROOT" # cmd = "env | ag ^ISIS" # s = lib.system(cmd) # print s # load SPICE kernels (data files) with camera and target positions, etc libspice.loadKernels() # read small dbs into memory # centeringInfo = lib.readCsv(config.dbCentering) # when to turn centering on/off retargetingInfo = lib.readCsv(config.dbRetargeting) # remapping listed targets csvPositions, fPositions = lib.openCsvReader(config.dbPositions) # for target size # # dictionary to keep track of last image file in target sequence (eg for Ariel flyby) # lastImageInTargetSequence = {} # size of 2d map mymax = 800 mxmax = 2 * mymax mxcenter = mxmax/2 mycenter = mymax/2 # set up blank mapping arrays: h(x,y) = (hx(x,y), hy(x,y)) # h tells map where to pull its pixels from - ie map(mx,my) = im(h(mx,my)) hx = np.zeros((mymax,mxmax),np.float32) hy = np.zeros((mymax,mxmax),np.float32) # make blank maps for each color channel #. each system-craft-target-camera-channel will need its own png map file to write to and colorize from # store in a map folder # bluemap = np.zeros((mymax,mxmax),np.float32) bluemap = np.zeros((mymax,mxmax),np.uint8) # each might also need a count map, if wind up averaging things together to smooth things out # would prefer to avoid it if possible though - adds more complexity and i/o countmap = np.zeros((mymax,mxmax),np.uint8) # iterate through all available images, filter on desired volume or image csvFiles, fFiles = lib.openCsvReader(config.dbFiles) nfile = 1 for rowFiles in csvFiles: volume = rowFiles[config.colFilesVolume] fileId = rowFiles[config.colFilesFileId] # filter to given volume # if volume!=filterVolume: continue # get image properties filter = rowFiles[config.colFilesFilter] system = rowFiles[config.colFilesSystem] craft = rowFiles[config.colFilesCraft] target = rowFiles[config.colFilesTarget] camera = rowFiles[config.colFilesCamera] time = rowFiles[config.colFilesTime] note = rowFiles[config.colFilesNote] # relabel target field if necessary target = lib.retarget(retargetingInfo, fileId, target) #. skip others if fileId!='C1465335': continue # get cube filename cubefile = lib.getFilepath('import', volume, fileId) if not os.path.isfile(cubefile): # print 'warning file not found', cubefile continue #. for now # get folders importSubfolder = lib.getSubfolder('import', volume) jpegSubfolder = importSubfolder + 'jpegs/' # mapSubfolder = importSubfolder + 'maps/' mapSubfolder = importSubfolder #. for now lib.mkdir(jpegSubfolder) lib.mkdir(mapSubfolder) # export as jpeg imagefile = jpegSubfolder + fileId + '.jpg' # if not os.path.isfile(imagefile): if 1: cmd = "isis2std from=%s to=%s format=jpeg" % (cubefile, imagefile) print cmd lib.system(cmd) # print 'Volume %s mapping %d: %s \r' % (volume,nfile,infile), # print 'Volume %s mapping %d: %s' % (volume,nfile,infile) nfile += 1 # get target code, eg Jupiter targetId = spice.bodn2c(target) # eg 'Jupiter'->599 # print target, targetId # get instrument spacecraft = -31 if craft=='Voyager1' else -32 spacecraftBus = spacecraft * 1000 # eg -31000 spacecraftScanPlatform = spacecraftBus - 100 # eg -31100 spacecraftNarrowCamera = spacecraftScanPlatform - 1 # eg -31101 spacecraftWideCamera = spacecraftScanPlatform - 2 # eg -31102 # instrument = spacecraftBus # eg -31000, use for NAIF continuous kernels instrument = spacecraftScanPlatform # eg -31100, use for PDS discrete kernels # get ephemeris time ephemerisTime = spice.str2et(time) # seconds since J2000 (will be negative) # world coordinate frame to use # (ECLIPB1950 is how the voyager and planet positions are encoded) frame = 'ECLIPB1950' # get world-to-camera matrix (camera pointing matrix) # C is a transformation matrix from ELIPB1950 to the instrument-fixed frame # at the given time C = getCameraMatrix(frame, spacecraft, instrument, ephemerisTime) print 'C=camera pointing matrix - transform world to camera coords' print C # get world-to-body matrix # B = getBodyMatrix(frame, targetId, ephemerisTime) B = spice.tipbod(frame, targetId, ephemerisTime) print 'B=world to body/target frame matrix' print B # get boresight vector # this is just the third row of the C-matrix, *per spice docs* boresight = C[2] print 'boresight pointing vector',boresight # get location of prime meridian rotationRate = 870.5366420 # deg/day for great red spot primeMeridian = rotationRate /24/60/60 * ephemerisTime # deg print 'primeMeridian (deg)', primeMeridian % 360 primeMeridianRadians = primeMeridian * math.pi/180 # get target position in world coordinates # ie vector from craft to target # and distance observer = 'Voyager ' + craft[-1] # eg Voyager 1 abberationCorrection = 'NONE' position, lightTime = spice.spkpos(target, ephemerisTime, frame, abberationCorrection, observer) # position = getObserverToTargetVector(frame, observer, target, ephemerisTime) distance = libspice.getDistance(position) posnormal = position / distance print 'target position relative to observer',position print 'distance in km',distance print 'position normalized', posnormal # see how different boresight and position vector are dot = np.dot(boresight, posnormal) theta = math.acos(dot) * 180/math.pi print 'angle between boresight and position vector', theta # what longitudes are visible? #.. get from position of craft visibleLongitudesMin = 0 visibleLongitudesMax = math.pi # get tilt of north pole relative to image y-axis npRadians = getNorthPoleAngle(target, position, C, B, camera) # get axial tilt rotation matrix cc = math.cos(npRadians) ss = math.sin(npRadians) mTilt = np.array([[cc,-ss],[ss,cc]]) # get expected angular size (as fraction of frame) and radius imageFraction = lib.getImageFraction(csvPositions, fileId) targetRadiusPx = int(400*imageFraction) #.param # read the (centered) image im = cv2.imread(imagefile) # draw the target's north pole on image # im = drawNorthPole(im) # build hx,hy arrays, which tell map where to pull pixels from in source image # m: map (0 to mxmax, 0 to mymax) r = targetRadiusPx # pixels for mx in xrange(mxmax): # eg 0 to 1600 for my in xrange(mymax): # eg 0 to 800 # q: map (0 to 2pi, -1 to 1) qx = float(mx) / mxmax * 2 * math.pi + primeMeridianRadians qx = qx % (2 * math.pi) # 0 to 2pi qy = -float(my-mycenter)/mycenter # 1 to -1 # s: image (-1 to 1, -1 to 1) sx = -math.sqrt(1 - qy**2) * math.cos(qx) # -1 to 1 sy = qy # 1 to -1 # rotate s to account for axial tilt relative to camera up axis # ie s = mTilt * s s = np.array([sx,sy]) s = np.dot(mTilt,s) sx,sy = s # p: image (0 to 800, 0 to 800) px = sx * r + 400 # 0 to 800 py = -sy * r + 400 # 0 to 800 # hx[my][mx] = px # hy[my][mx] = py visible = (qx >= visibleLongitudesMin) and (qx <= visibleLongitudesMax) if visible: hx[my][mx] = px hy[my][mx] = py else: hx[my][mx] = 0 hy[my][mx] = 0 # do map projection map = cv2.remap(im, hx, hy, cv2.INTER_LINEAR) map = map[:,:,0] # map = np.array(map, np.float32) libimg.show(map) # save map as png file mapfile = mapSubfolder + fileId + '-map.png' cv2.imwrite(mapfile, map) sys.exit(0) # #. now need to blend this into the main map for this filter # # print type(map[0][0]) # # print type(bluemap[0][0]) # # bluemap = cv2.addWeighted(bluemap, 0.5, map, 0.5, 0) # # ret, countzero = cv2.threshold(countmap, 0,255, cv2.THRESH_BINARY) # # ret, countone = cv2.threshold(countmap, 0,255, cv2.THRESH_BINARY) # # countzero = 255-countone # # ret, mapMask = cv2.threshold(map, 1, 255, cv2.THRESH_BINARY) # # mapMask = cv2.bitwise_and(countzero, mapMask) # # c = mapMask & 1 # # countmap += c # # libimg.show(countmap) # # ret, mapnonzero = cv2.threshold(map, 0,1, cv2.THRESH_BINARY) # ret, mapnonzero = cv2.threshold(map, 1,1, cv2.THRESH_BINARY) # countmapPlusOne = countmap + 1 # countmap2 = countmap + 1-mapnonzero # base = np.array(bluemap, np.float32) # # base = base * countmap / countmapPlusOne # base = base * countmap2 / countmapPlusOne # newmap = np.array(map, np.float32) # newmap = newmap / countmapPlusOne # newbase = base + newmap # newbase = np.array(newbase, np.uint8) # # increment countmap where map image data exists # countmap += mapnonzero # # countmap = cv2.bitwise_and(countmap, mapnonzero) # countmap = np.clip(countmap, 0, 4) # bluemap = newbase # libimg.show(bluemap) # # # libimg.show(mapMask) # # mapMaskInv = 255-mapMask # # # libimg.show(mapMaskInv) # # bluemapSame = cv2.bitwise_and(bluemap, mapMaskInv) # # bluemapChange = cv2.bitwise_and(bluemap, mapMask) # # bluemapChange = cv2.addWeighted(bluemapChange, 0.5, map, 0.5, 0) # # # bluemapNew = cv2.bitwise_and(map, mapMask) # # bluemap = bluemapSame + bluemapChange # # # bluemap = bluemapSame + bluemapNew # # libimg.show(bluemap) # # bluemap = cv2.bitwise_and(bluemap, mapMaskInv) # # libimg.show(bluemap) # # bluemap = bluemap + map # # libimg.show(bluemap) # # if nfile>0: # # if nfile>3: # # if nfile>5: # if nfile>8: # sys.exit(0) fPositions.close() fFiles.close() print
def __Geometry(self, boresight=''): #if self.geometry_flag is True and \ # self.time.window.all() == self.previous_tw.all(): # return distance = [] altitude = [] boresight_latitude = [] boresight_longitude = [] latitude = [] longitude = [] subpoint_xyz = [] subpoint_pgc = [] subpoint_pcc = [] zaxis_target_angle = [] myaxis_target_angle = [] yaxis_target_angle = [] xaxis_target_angle = [] beta_angle = [] qs, qx, qy, qz = [], [], [] ,[] x, y, z = [],[],[] tar = self.target time = self.time for et in time.window: try: # # Compute the distance # ptarg, lt = spiceypy.spkpos(tar.name, et, tar.frame, time.abcorr, self.name) x.append(ptarg[0]) y.append(ptarg[1]) z.append(ptarg[2]) vout, vmag = spiceypy.unorm(ptarg) distance.append(vmag) # # Compute the geometric sub-observer point. # if tar.frame == 'MARSIAU': tar_frame = 'IAU_MARS' else: tar_frame = tar.frame spoint, trgepc, srfvec = spiceypy.subpnt(tar.method, tar.name, et, tar_frame, time.abcorr, self.name) subpoint_xyz.append(spoint) # # Compute the observer's altitude from SPOINT. # dist = spiceypy.vnorm(srfvec) altitude.append(dist) # # Convert the sub-observer point's rectangular coordinates to # planetographic longitude, latitude and altitude. # spglon, spglat, spgalt = spiceypy.recpgr(tar.name, spoint, tar.radii_equ, tar.flat) # # Convert radians to degrees. # spglon *= spiceypy.dpr() spglat *= spiceypy.dpr() subpoint_pgc.append([spglon, spglat, spgalt]) # # Convert sub-observer point's rectangular coordinates to # planetocentric radius, longitude, and latitude. # spcrad, spclon, spclat = spiceypy.reclat(spoint) # # Convert radians to degrees. # spclon *= spiceypy.dpr() spclat *= spiceypy.dpr() subpoint_pcc.append([spclon, spclat, spcrad]) latitude.append(spclat) #TODO: Remove with list extraction longitude.append(spclon) # TODO: Remove with list extraction # # Compute the geometric sub-boresight point. # if tar.frame == 'MARSIAU': tar_frame = 'IAU_MARS' else: tar_frame = tar.frame if boresight: try: id = spiceypy.bodn2c(boresight) (shape,framen, bsight, n, bounds) = spiceypy.getfov(id, 80) mat = spiceypy.pxform(framen,tar_frame,et) except: framen = boresight bsight = 0,0,1 else: bsight = self.name try: if tar.method == 'INTERCEPT/ELLIPSOID': method = 'ELLIPSOID' else: method = tar.method spoint, trgepc, srfvec = spiceypy.sincpt(method, tar.name, et, tar_frame, time.abcorr, self.name, framen, bsight) # # Convert the sub-observer point's rectangular coordinates to # planetographic longitude, latitude and altitude. # spglon, spglat, spgalt = spiceypy.recpgr(tar.name, spoint, tar.radii_equ, tar.flat) # # Convert radians to degrees. # spglon *= spiceypy.dpr() spglat *= spiceypy.dpr() # # Convert sub-observer point's rectangular coordinates to # planetocentric radius, longitude, and latitude. # spcrad, spclon, spclat = spiceypy.reclat(spoint) # # Convert radians to degrees. # spclon *= spiceypy.dpr() spclat *= spiceypy.dpr() boresight_latitude.append(spclat) boresight_longitude.append(spclon) except: pass # # Compute the angle between the observer's S/C axis and the # geometric sub-observer point # obs_tar, ltime = spiceypy.spkpos(tar.name, et, 'J2000', time.abcorr, self.name) obs_zaxis = [0, 0, 1] obs_myaxis = [0, -1, 0] obs_yaxis = [0, 1, 0] obs_xaxis = [1, 0, 0] # # We need to account for when there is no CK attitude available. # try: matrix = spiceypy.pxform(self.frame, 'J2000', et) z_vecout = spiceypy.mxv(matrix, obs_zaxis) zax_target_angle = spiceypy.vsep(z_vecout, obs_tar) zax_target_angle *= spiceypy.dpr() zaxis_target_angle.append(zax_target_angle) my_vecout = spiceypy.mxv(matrix, obs_myaxis) myax_target_angle = spiceypy.vsep(my_vecout, obs_tar) myax_target_angle *= spiceypy.dpr() myaxis_target_angle.append(myax_target_angle) y_vecout = spiceypy.mxv(matrix, obs_myaxis) yax_target_angle = spiceypy.vsep(y_vecout, obs_tar) yax_target_angle *= spiceypy.dpr() yaxis_target_angle.append(yax_target_angle) x_vecout = spiceypy.mxv(matrix, obs_myaxis) xax_target_angle = spiceypy.vsep(x_vecout, obs_tar) xax_target_angle *= spiceypy.dpr() xaxis_target_angle.append(xax_target_angle) quat = spiceypy.m2q(spiceypy.invert(matrix)) qs.append(quat[0]) qx.append(-1*quat[1]) qy.append(-1*quat[2]) qz.append(-1*quat[3]) except: zaxis_target_angle.append(0.0) myaxis_target_angle.append(0.0) yaxis_target_angle.append(0.0) xaxis_target_angle.append(0.0) qs.append(0.0) qx.append(0.0) qy.append(0.0) qz.append(0.0) beta_angle.append(spiops.beta_angle(self.name, self.target.name, et)) except: boresight_latitude = 0 boresight_longitude = 0 distance = 0 altitude = 0 latitude = 0 longitude = 0 subpoint_xyz = [0,0,0] subpoint_pgc = [0,0,0] subpoint_pcc = [0,0,0] zaxis_target_angle = 0 myaxis_target_angle = 0 yaxis_target_angle = 0 xaxis_target_angle = 0 beta_angle = 0 (qx, qy, qz, qs) = 0, 0, 0, 0 (x, y, z) = 0, 0, 0 self.boresight_latitude = boresight_latitude self.boresight_longitude = boresight_longitude self.distance = distance self.altitude = altitude self.latitude = latitude self.longitude = longitude self.subpoint_xyz = subpoint_xyz self.subpoint_pgc = subpoint_pgc self.subpoint_pcc = subpoint_pcc self.zaxis_target_angle = zaxis_target_angle self.myaxis_target_angle = myaxis_target_angle self.yaxis_target_angle = yaxis_target_angle self.xaxis_target_angle = xaxis_target_angle self.beta_angle = beta_angle self.quaternions = [qx, qy, qz, qs] self.trajectory = [x,y,z] self.geometry_flag = True self.previous_tw = self.time.window return
def __init__(self, kernels_folder=None): """ Parameters: kernels_folder (string): If not provided, the path stored in the environment variable ``TESSPHOT_SPICE_KERNELS`` is used, and if that is not set, the ``data/spice`` directory is used. """ logger = logging.getLogger(__name__) # If no kernel folder is given, used the one stored in env.var. or the default location: if kernels_folder is None: kernels_folder = os.environ.get( 'TESSPHOT_SPICE_KERNELS', os.path.join(os.path.dirname(__file__), 'data', 'spice')) # Make sure the kernel directory exists: kernels_folder = os.path.abspath(kernels_folder) os.makedirs(kernels_folder, exist_ok=True) # Automatically download kernels from TASOC, if they don't already exist? #urlbase = 'https://archive.stsci.edu/missions/tess/models/' urlbase = 'https://tasoc.dk/pipeline/spice/' downlist = [] for fname in self.kernel_files: fpath = os.path.join(kernels_folder, fname) if not os.path.exists(fpath): downlist.append([urlbase + fname, fpath]) if downlist: download_parallel(downlist) # Path where meta-kernel will be saved: hashkey = kernels_folder + ',' + ','.join(self.kernel_files) fileshash = hashlib.md5(hashkey.encode()).hexdigest() self.METAKERNEL = os.path.join(kernels_folder, 'metakernel-' + fileshash + '.txt') # Write meta-kernel to file: if not os.path.exists(self.METAKERNEL): with open(self.METAKERNEL, 'w') as fid: fid.write("KPL/MK\n") fid.write(r"\begindata" + "\n") fid.write("PATH_VALUES = ('" + kernels_folder + "')\n") fid.write("PATH_SYMBOLS = ('KERNELS')\n") fid.write("KERNELS_TO_LOAD = (\n") fid.write(",\n".join([ "'$KERNELS/" + fname + "'" for fname in self.kernel_files ])) fid.write(")\n") fid.write(r"\begintext" + "\n") fid.write("End of MK file.\n") # Because SpiceyPy loads kernels into a global memory scope (BAAAAADDDD SpiceyPy!!!), # we first check if we have already loaded this into the global scope: # This is to attempt to avoid loading in the same kernels again and again when # running things in parallel. already_loaded = False for k in range(spiceypy.ktotal('META')): if os.path.abspath(spiceypy.kdata(k, 'META')[0]) == self.METAKERNEL: logger.debug("SPICE Meta-kernel already loaded.") already_loaded = True break # Define TESS object if it doesn't already exist: try: spiceypy.bodn2c('TESS') except SpiceyError: logger.debug("Defining TESS name in SPICE") spiceypy.boddef('TESS', -95) # Load kernels if needed: if not already_loaded: logger.debug("Loading SPICE Meta-kernel: %s", self.METAKERNEL) spiceypy.furnsh(self.METAKERNEL) # Let's make sure astropy is using the de430 kernels as well: # Default is to use the same as is being used by SPOC (de430). # If using astropy 4.0+, we can load the local one directly. Before this, # it needs to be downloaded and cached: # NOTE: https://github.com/astropy/astropy/pull/8767 if astropy_major_version >= 4: self.planetary_ephemeris = os.path.join( kernels_folder, 'tess2018338154429-41241_de430.bsp') else: self.planetary_ephemeris = urlbase + 'tess2018338154429-41241_de430.bsp' self._old_solar_system_ephemeris = coord.solar_system_ephemeris.get() coord.solar_system_ephemeris.set(self.planetary_ephemeris)
ref = "J2000" observer = "-143" #observer target = "SUN" datetime_start = datetime(year=2018, month=4, day=21) datetimes = [datetime_start + timedelta(days=x) for x in range(365 * 4)] date_strs = [datetime.strftime(x, "%Y-%m-%d") for x in datetimes] date_ets = [sp.str2et(x) for x in date_strs] tgo_pos = np.asfarray([ sp.spkpos(target, time, ref, abcorr, observer)[0] for time in list(date_ets) ]) tgo_dist = la.norm(tgo_pos, axis=1) code = sp.bodn2c(target) pradii = sp.bodvcd(code, 'RADII', 3) # 10 = Sun sun_radius = pradii[1][0] sun_diameter_arcmins = np.arctan(sun_radius / tgo_dist) * sp.dpr() * 60.0 * 2.0 fig, ax = plt.subplots(figsize=(FIG_X, FIG_Y - 1)) ax.plot_date(datetimes, sun_diameter_arcmins, linestyle="-", ms=0) ax.set_xlabel("Date") ax.set_ylabel("Solar diameter as seen from TGO (arcminutes)") ax.set_title("Apparent diameter of Sun since start of TGO mission") ax.xaxis.set_major_locator(MonthLocator(bymonth=None, interval=6, tz=None)) fig.tight_layout() if SAVE_FIGS:
def name(self, name): self._name = name try: self._id = spiceypy.bodn2c(name) except spiceytypes.SpiceyError: raise ValueError(f'Body name "{name}" not known by SPICE')
def _target_id(self): return spice.bodn2c(self.label['TARGET_NAME'])