Пример #1
0
def find_maven_apsis(segment='periapse'):
    """
    Calculates the ephemeris times at apoapse or periapse for all MAVEN orbits between orbital insertion and now.
    Requires furnishing of all SPICE kernels.

    Parameters
    ----------
    segment : str
        The orbit point at which to calculate the ephemeris time. Choices are 'periapse' and 'apoapse'. Defaults to
        'periapse'.

    Returns
    -------
    orbit_numbers : array
        Array of MAVEN orbit numbers.
    et_array : array
        Array of ephemeris times for chosen orbit segment.
    """

    # set starting and ending times
    et_start = 464623267  # MAVEN orbital insertion
    et_end = spice.datetime2et(datetime.utcnow())  # right now

    # do very complicated SPICE stuff
    target = 'Mars'
    abcorr = 'NONE'
    observer = 'MAVEN'
    relate = ''
    refval = 0.
    if segment == 'periapse':
        relate = 'LOCMIN'
        refval = 3396. + 500.
    elif segment == 'apoapse':
        relate = 'LOCMAX'
        refval = 3396. + 6200.
    adjust = 0.
    step = 60.  # 1 minute steps, since we are only looking within periapse segment for periapsis
    et = [et_start, et_end]
    cnfine = spice.utils.support_types.SPICEDOUBLE_CELL(2)
    spice.wninsd(et[0], et[1], cnfine)
    ninterval = round((et[1] - et[0]) / step)
    result = spice.utils.support_types.SPICEDOUBLE_CELL(round(1.1 * (et[1] - et[0]) / 4.5))
    spice.gfdist(target, abcorr, observer, relate, refval, adjust, step, ninterval, cnfine, result=result)
    count = spice.wncard(result)
    et_array = np.zeros(count)
    if count == 0:
        print('Result window is empty.')
    else:
        for i in range(count):
            lr = spice.wnfetd(result, i)
            left = lr[0]
            right = lr[1]
            if left == right:
                et_array[i] = left

    # make array of orbit numbers
    orbit_numbers = np.arange(1, len(et_array) + 1, 1, dtype=int)

    # return orbit numbers and array of ephemeris times
    return orbit_numbers, et_array
Пример #2
0
    def computeOccultations(self, observer_in, occultation_bodies_in,
                            target_in, target_shape_in, target_frame_in,
                            step_size_in, aberration_correction_in,
                            greg_format_string_in):

        self.greg_format_string = greg_format_string_in
        split_string = self.greg_format_string.split(' ')
        self.time_system_string = [i for i in split_string if '::' in i][0][2:]

        self.observer = str(observer_in)
        self.occultation_bodies = occultation_bodies_in
        self.target = target_in
        self.target_shape = target_shape_in
        self.target_frame = target_frame_in
        self.search_step_size = step_size_in
        self.aberration_correction = aberration_correction_in
        self.cumulative_results = {}
        for body in self.occultation_bodies:
            # add a new key to the results dictionary for this body
            self.cumulative_results[body.name] = []
            for occultation_type in body.occultation_types:
                body.search_start_ET_seconds = spice.str2et(body.search_start)
                body.search_end_ET_seconds = spice.str2et(body.search_end)
                spice.wninsd(body.search_start_ET_seconds,
                             body.search_end_ET_seconds,
                             self.SPICE_search_window)
                spice.gfoclt(occultation_type, body.name, body.shape,
                             body.frame, self.target, self.target_shape,
                             self.target_frame, self.aberration_correction,
                             self.observer, self.search_step_size,
                             self.SPICE_search_window, self.results_window)
                winsiz = spice.wncard(self.results_window)
                for body_index in range(winsiz):
                    [intbeg, intend] = spice.wnfetd(self.results_window,
                                                    body_index)
                    btmstr = spice.timout(intbeg, self.greg_format_string)
                    etmstr = spice.timout(intend, self.greg_format_string)
                    occultation_event = OccultationEvent()
                    occultation_event.start = btmstr
                    occultation_event.stop = etmstr
                    occultation_event.start_JD = greg2Julian(btmstr)
                    occultation_event.stop_JD = greg2Julian(etmstr)
                    occultation_event.duration = intend - intbeg
                    occultation_event.type = occultation_type
                    occultation_event.observer = self.observer
                    occultation_event.occulting_body = body.name
                    occultation_event.occulting_body_shape = body.shape
                    occultation_event.target = self.target
                    occultation_event.target_body_shape = self.target_shape
                    self.cumulative_results[body.name].append(
                        occultation_event)
Пример #3
0
    def check_coverage(self):
        cover = spice.utils.support_types.SPICEDOUBLE_CELL(2000)
        if local == 0:
            spice.spkcov(auxdir + 'spk/MSGR_HGM008_INTGCB.bsp', -236, cover)
        else:
            spice.spkcov(
                '/home/sberton2/Works/NASA/Mercury_tides/spktst/MSGR_HGM008_INTGCB.bsp',
                -236, cover)

        twind = [spice.wnfetd(cover, i) for i in range(spice.wncard(cover))]
        epo_in = np.sort(self.ladata_df.ET_TX.values)
        self.ladata_df['in_spk'] = np.array(
            [np.sum([t[0] <= val <= t[1] for t in twind])
             for val in epo_in]) > 0
        if debug:
            print(len(self.ladata_df.loc[self.ladata_df['in_spk'] == False]))
            print("lensel", len(self.ladata_df),
                  len(self.ladata_df.loc[self.ladata_df['in_spk']]))
        self.ladata_df = self.ladata_df.loc[self.ladata_df['in_spk']]
def core():
    class SpiceVariables:
        obs = '-74'  # NAIF code for MEX
        target = 'MARS ODYSSEY'  # NAIF code for TGO ['EARTH'/'SUN'/ a groundstation etc]
        obsfrm = 'IAU_MARS'
        abcorr = 'NONE'
        crdsys = 'LATITUDINAL'
        coord = 'LATITUDE'
        stepsz = 100.0  # Check every 300 seconds if there is an occultation
        MAXILV = 100000  #Max number of occultations that can be returned by gfoclt
        bshape = 'POINT'
        fshape = 'DSK/UNPRIORITIZED'
        front = 'MARS'
        fframe = 'IAU_MARS'
        TFMT = 'YYYY-MM-DD HR:MN:SC'  # Format that Cosmographia understands

    sv = SpiceVariables()

    #-----------------------------------------------------<VALUES TO EDIT REGULARLY>----------------------------------------
    # If you only wish to analysis mutual [cross-link] occultation between MEX and TGO, then this is the only section that
    # needs to be edited
    start = '2020 MAR 1'
    stop = '2020 MAR 3'
    OCCSELECTION = 17  # Which occultation do you wish to see in Cosmographia? [optional]
    here = path.abspath(path.dirname(__file__))
    PathtoMetaKernel1 = here + '/TGO/krns/mk/em16_plan.tm'
    PathtoMetaKernel2 = here + '/MEX/krns/mk/MEX_OPS.tm'
    #-----------------------------------------------------------------------------------------------------------------------

    spice.furnsh(PathtoMetaKernel1)
    spice.furnsh(PathtoMetaKernel2)

    sv = SpiceVariables()

    # Setting Variables
    ingresslist = np.array([1.0], dtype=float)
    etbeg = spice.str2et(start)
    etend = spice.str2et(stop)

    # Form a windows that gfoclt can populate
    window = stypes.SPICEDOUBLE_CELL(2)
    spice.wninsd(etbeg, etend, window)
    occwindow = stypes.SPICEDOUBLE_CELL(sv.MAXILV)

    #find occultation windows between the dates listed above [ most comp cost in this function]
    spice.gfoclt('ANY', sv.front, sv.fshape, sv.fframe, sv.target, sv.bshape,
                 'J2000', sv.abcorr, sv.obs, sv.stepsz, window, occwindow)

    winsiz = spice.wncard(occwindow)  # Find cardinality (number of windows)

    #initialize lists to form dataframe
    lon, lat, dist, sza, angle = (np.ones(winsiz - 1) for i in range(5))

    # Enter the ingress epochs into a dataframe
    occlist = np.ones((winsiz, 3))
    for i in range(winsiz):
        [ingress, egress
         ] = spice.wnfetd(occwindow,
                          i)  # extract the begining and ends of the windows
        if i == 1:
            ingresslist = ingress
        else:
            ingresslist = np.append(ingresslist, [ingress])

    occs = pd.DataFrame(ingresslist, columns=['Time'])
    occ = occs.Time[OCCSELECTION]

    return occ


# sv = main.SpiceVariables()
# occ = core()
#print("strange result:", occ)

# #print(result)

# #form the dataframe
# length = 120
# occs = pd.DataFrame(ingresslist, columns=['Time'])
# residualdoppler = np.zeros(length)
# velocitydoppler = np.zeros(length)
# RESIDUALSUM = np.zeros(length)
# #Calculate the residual doppler as the sum of the neutral and ionosphere
# tic  = timer.perf_counter()
# for time in tqdm(range(length)): #begin time at occultation epoch and go to 2 mins pior

#     ray, dist, totalperiods, remainingdistance = main.producegeometrylamda(occs.Time[OCCSELECTION], sv, time*8)# Produce geometry in wavelenghts to investigate electric distance

#     _,_,_,_, alt = main.Location(occs.Time[OCCSELECTION], sv, time*8)

#     ionoresidual = atmosphere.iono(ray[2,:],totalperiods)
#     neutralresidual = atmosphere.neutral(ray[2,:],totalperiods)
#     residual = 1 + (ionoresidual + neutralresidual)

#     # plt.plot(range(totalperiods),residual[0,:])
#     # plt.title("Refractive Index through Propergation of Ray")
#     # plt.xlabel("MEX->TGO distance (km)")
#     # plt.ylabel("Refractive Index")
#     # plt.show()

#     #DO A TEST TO SEE IF THE NET REFRACTIVE INDEX CHANGES OVER TIME, THEN U CAN HONE IN ON THE ERRRO
#     # account for the plus 1

#     [electricdistance, geometric, resdopplershift] = main.doppler(residual, totalperiods, dist, remainingdistance)
#     miss = electricdistance - dist + remainingdistance # a possitive number as electric distance is greater that geometric due to iono
#     howwrongurcodeis = geometric - dist # is this purly due to rounding of that 9945 (each 1 is ~685 m)
#     residualdoppler[time] = resdopplershift

#     #find the geometric doppler too UNTESTED MODULE
#     sc2scstates = spice.spkezr(sv.target, (occs.Time[OCCSELECTION] - time*8), sv.fframe, 'LT+S', sv.obs)
#     velocityvector = sc2scstates[0][3:6]
#     velocityvector = velocityvector[0:3]
#     positionalvector =  sc2scstates[0][0:3]
#     positionalvector = positionalvector[0:3]
#     velocityangle = spice.vsep( positionalvector, velocityvector) #rads
#     relativevelocity = np.linalg.norm(velocityvector) * np.cos(velocityangle)
#     geometricdopplershift = -(relativevelocity/constants.c) * 437.1e6
#     velocitydoppler[time] = geometricdopplershift *1000 # becuase spice is in km and c is in m

# toc = timer.perf_counter()
# passingtime = toc-tic
# print('elapsed time in seconds:', passingtime)
# noise = np.random.normal(0,50,velocitydoppler.shape)
# RESIDUALSUM = residualdoppler + velocitydoppler + noise

# fig, ax = plt.subplots(3)
# ax[0].plot(range(-960,0,8),velocitydoppler[: : -1] )
# ax[0].set_title('Geometric', loc='left')
# ax[0].set_xlabel('Time after Occ (s)')
# ax[0].set_ylabel('Doppler Shift (Hz)')
# ax[1].plot(range(-960,0,8),residualdoppler[: : -1] )
# ax[1].set_title('Residual', loc='left')
# ax[1].set_xlabel('Time after Occ (s)')
# ax[1].set_ylabel('Doppler Shift (Hz)')
# ax[2].plot(range(-960,0,8),RESIDUALSUM[: : -1])
# ax[2].set_title('Product + Noise', loc='left')
# ax[2].set_xlabel('Time to Horizon Epoch (s)')
# ax[2].set_ylabel('Doppler Shift (Hz)')
# plt.show()

# #then add noise
    equals,
) = 'earth 0 j2000 none lt lt+s ra/dec declination ='.upper().split()

sp.furnsh('de421.bsp')

gaia_db = 'gaia.sqlite3'

### Find ET of spring equinox wrt Solar System Barycenter (SSB) & earth
cnfine = sp.utils.support_types.SPICEDOUBLE_CELL(2)
result = sp.utils.support_types.SPICEDOUBLE_CELL(200)
sp.wninsd(0., sp.pi() * .5e7, cnfine)
r = sp.gfposc(ssb, j2000, none, earth, ra_dec, declination, equals, 0., 0.,
              10 * sp.spd(), 200, cnfine, result)

### Get SSB->Earth and Earth->SSB vectors at that time
et = sp.wnfetd(result, 0)[0]
ssb2e = sp.spkezr(earth, et, j2000, none, ssb)[0]
e2ssblt = sp.spkezr(ssb, et, j2000, LT, earth)[0]
if do_debug:
    ### N.B. Light-time correction on Earth->SSB will have no effect
    print(
        dict(
            etcal=sp.etcal(et, 99),
            ssb2earth_au=sp.vscl(aupkm, ssb2e[:3]),
            earth2ssb_au=sp.vscl(aupkm, e2ssblt[:3]),
        ))
    assert 0.0 == sp.vnormg(sp.vaddg(ssb2e, e2ssblt, 6), 6)

### Get a star (ra,dec,parallax) near the pole with parallax > 10mas/y
cn = sl3.connect(gaia_db)
cu = cn.cursor()
    ### Find local minima using SPICE Geometry Finder
    sp.gfpa(sClock, sMinute, "NONE", sHour, "LOCMIN", 1e-6, 0.0, spm, 6000,
            cnfine, result)

    ### Confirm that 22 minima were found
    assert 22 == sp.wncard(result)
    print('SP-Kernel passed [{}-alignments per day] test'.format(
        sp.wncard(result)))

    ### Optional logging
    if doDebug:
        print('Alignments between {} and {}:'.format(
            sp.etcal(etStart + 0.0005, 99), sp.etcal(etStop + 0.0005, 99)))
        for iWin in range(sp.wncard(result)):
            left, right = sp.wnfetd(result, iWin)
            print('  {}:  {}'.format(iWin, sp.etcal(left + 0.0005, 99)))

    ### Repeat with extra time
    cnfine = sp.stypes.SPICEDOUBLE_CELL(2)
    etStop = et0 + spd + 10e-3
    sp.wninsd(etStart, etStop, cnfine)

    ### Find local minima
    sp.gfpa(sClock, sMinute, "NONE", sHour, "LOCMIN", 1e-6, 0.0, spm, 6000,
            cnfine, result)

    ### Confirm that 23 minima were found
    assert 23 == sp.wncard(result)
    print('SP-Kernel passed [{}-alignments per (day+20ms)] test'.format(
        sp.wncard(result)))
Пример #7
0
    spice.furnsh(sd.pck00010)

    sc = SC({
        'date0': '2021-03-03 22:10:35 TDB',
        'coes': [7480.0, 0.09, 5.5, 6.26, 5.95, 0.2],
        'tspan': '40',
    })

    st.write_bsp(sc.ets, sc.states[:, :6], {'bsp_fn': 'leo.bsp'})
    spice.furnsh('leo.bsp')

    et0 = spice.str2et('2021-03-03 22:10:40 TDB')
    etf = spice.str2et('2021-03-04 TDB')

    timecell = spice.utils.support_types.SPICEDOUBLE_CELL(2)
    spice.appndd(et0, timecell)
    spice.appndd(etf, timecell)

    cell = spice.gfoclt('ANY', '399', 'ELLIPSOID', 'IAU_EARTH', '10',
                        'ELLIPSOID', 'IAU_SUN', 'LT', '-999', 120.0, timecell)
    ets_SPICE = spice.wnfetd(cell, 0)
    cal0 = spice.et2utc(ets_SPICE[0], 'C', 1)
    cal1 = spice.et2utc(ets_SPICE[1], 'C', 1)
    print('\n*** SPICE RESULTS ***')
    print(f'{cal0} --> {cal1}')

    sc.plot_3d()
    sc.calc_eclipses(vv=True)

    sc.plot_eclipse_array({'time_unit': 'seconds', 'show': True})
Пример #8
0
def find_segment_et(orbit_number, data_directory, segment='apoapse'):
    """Calculates the ephemeris time at the moment of apoapsis or
    periapsis for an orbit. Requires data files exist for the choice
    of orbit and segment. If not, use the full-mission
    "find_maven_apsis" function available in the "data"
    sub-module. Also requires furnishing of all SPICE kernels.

    Parameters
    ----------
    orbit_number : int
        The MAVEN orbit number.
    data_directory : str
        Absolute system path to the location containing orbit block
        folders ("orbit01300", orbit01400", etc.)
    segment : str
        For which orbit segment you want to calculate the ephemeris
        time. Options are 'apoapse' and 'periapse." Default choice is
        'apoapse'.

    Returns
    -------
    et : float
        The ephemeris time for the chosen segment/orbit number.

    """

    # load files
    files = find_files(orbit=orbit_number,
                       segment=segment,
                       data_directory=data_directory)
    if len(files) == 0:
        raise Exception('No %s files for orbit %i.' % (segment, orbit_number))

    hdul = files[0]
    et_start = hdul['integration'].data['et'][0]

    # do very complicated SPICE stuff
    target = 'Mars'
    abcorr = 'NONE'
    observer = 'MAVEN'
    relate = ''
    refval = 0.
    if segment == 'periapse':
        relate = 'LOCMIN'
        refval = 3396. + 500.
    elif segment == 'apoapse':
        relate = 'LOCMAX'
        refval = 3396. + 6200.
    adjust = 0.
    step = 60.
    et = [et_start, et_start + 4800]
    cnfine = spice.utils.support_types.SPICEDOUBLE_CELL(2)
    spice.wninsd(et[0], et[1], cnfine)
    ninterval = 100
    result = spice.utils.support_types.SPICEDOUBLE_CELL(100)
    spice.gfdist(target,
                 abcorr,
                 observer,
                 relate,
                 refval,
                 adjust,
                 step,
                 ninterval,
                 cnfine,
                 result=result)
    et = spice.wnfetd(result, 0)[0]

    # return the ephemeris time of the orbit segment
    return et
Пример #9
0
    def find_maven_apsis_et(self, end_time: datetime = None,
                            apsis: str = 'apoapse') \
            -> tuple[np.ndarray, np.ndarray]:
        """Calculate the ephemeris times at either orbital apses between a start
        and end time. To do this, SPICE checks the Mars-MAVEN distance in steps
        of 1 minute, and returns the local minima (for periapsis) or local
        maxima (for apoapsis).

        Parameters
        ----------
        end_time
            Ending datetime to get apsis ephemeris times for. Default is
            :code:`None`, which will get times up until today.
        apsis
            The apsis to get the ephemeris times for. Can be either 'apoapse' or
            'periapse'.

        Returns
        -------
        orbits
            Relative MAVEN orbit numbers between the start and end dates.
        et
            Ephemeris times at the given apsis for all the orbits in the time
            range.

        """
        # TODO: it'd be nice to be able to specify a start time. This is easy to
        #  implement here, but I'm not sure how I'd compute orbit numbers.
        et_start = 464623267
        et_end = spice.datetime2et(datetime.utcnow()) if end_time is None \
            else spice.datetime2et(end_time)

        relate, refval = self.__set_apsis_flags(apsis)
        adjust = 0.
        step = 60.

        cnfine = spice.utils.support_types.SPICEDOUBLE_CELL(2)
        spice.wninsd(et_start, et_end, cnfine)
        ninterval = round((et_end - et_start) / step)
        result = spice.utils.support_types.SPICEDOUBLE_CELL(
            round(1.1 * (et_end - et_start) / 4.5))

        spice.gfdist(self.__target,
                     self.__abcorr,
                     self.__observer,
                     relate,
                     refval,
                     adjust,
                     step,
                     ninterval,
                     cnfine,
                     result=result)
        count = spice.wncard(result)
        et_array = np.zeros(count)
        if count == 0:
            print('Result window is empty.')
        else:
            for i in range(count):
                lr = spice.wnfetd(result, i)
                left = lr[0]
                right = lr[1]
                if left == right:
                    et_array[i] = left

        # make array of orbit numbers
        orbit_numbers = np.arange(1, len(et_array) + 1, 1, dtype=int)

        # return orbit numbers and array of ephemeris times
        return orbit_numbers, et_array
Пример #10
0
def cov_int(object_cov,
            object_id,
            kernel,
            time_format='TDB',
            global_boundary=False,
            report=False):
    """
    Generates a list of time windows out of a SPICE cell for which either
    the SPICE API spkcov_c or ckcov_c have been run.


    :param object_cov: SPICE
    :type object_cov:
    :param object_id: Object ID or Name for which we provide the coverage
    :type object_id: Union[str, int]
    :param kernel: Kernel name for which the coverage is being checked
    :type kernel: str
    :param time_format: Desired output format; 'UTC' or 'CAL'
    :type time_format: str
    :param global_boundary: Boolean to indicate whether if we want all the coverage windows or only the absolute start and finish coverage times
    :type global_boundary: bool
    :param report: If True prints the resulting coverage on the screen
    :type report: bool
    :return: Time Windows in the shape of a list
    :rtype: list
    """
    boundaries = False

    if '/' in kernel:
        kernel = kernel.split('/')[-1]

    #
    # Reporting should only be activated if we are not asking for global
    # boundaries.
    #
    if report and not global_boundary:

        try:
            body_name = spiceypy.bodc2n(object_id)
        except:
            body_name = spiceypy.frmnam(object_id, 60)

        print("Coverage for {} in {} [{}]:".format(body_name, kernel,
                                                   time_format))

    number_of_intervals = list(range(spiceypy.wncard(object_cov)))
    interval_start_list = []
    interval_finish_list = []
    coverage = []

    for element in number_of_intervals:
        et_boundaries = spiceypy.wnfetd(object_cov, element)

        if time_format == 'CAL' or time_format == 'UTC':
            boundaries = et2cal(et_boundaries, format=time_format)
        else:
            boundaries = et_boundaries

        interval_start = boundaries[0]
        interval_finish = boundaries[1]

        if report and not global_boundary:

            print("Interval {}: {} - {}\n".format(element, boundaries[0],
                                                  boundaries[1]))

        coverage.append(interval_start)
        coverage.append(interval_finish)
        interval_start_list.append(interval_start)
        interval_finish_list.append(interval_finish)

    #
    # If the global_boundary parameter is set the only output is the global
    # coverage start and finish
    #
    if global_boundary:

        start_time = min(interval_start)
        finish_time = max(interval_finish)

        coverage = et2cal([start_time, finish_time], format=time_format)

    return coverage
occwindow = stypes.SPICEDOUBLE_CELL(sv.MAXILV)

# find occultation windows between the dates listed above [ most comp cost in this function]
spice.gfoclt('ANY', sv.front, sv.fshape, sv.fframe, sv.target, sv.bshape, '',
             sv.abcorr, sv.obs, sv.stepsz, window, occwindow)

winsiz = spice.wncard(occwindow)  # Find cardinality (number of windows)

# initialize lists to form dataframe
lon, lat, dist, sza, angle = (np.ones(winsiz - 1) for i in range(5))

# Enter the ingress epochs into a dataframe
occlist = np.ones((winsiz, 3))
for i in range(winsiz):
    # extract the begining and ends of the windows
    [ingress, egress] = spice.wnfetd(occwindow, i)
    if i == 1:
        ingresslist = ingress
        egresslist = egress
    else:
        ingresslist = np.append(ingresslist, [ingress])
        egresslist = np.append(egresslist, [egress])

#MEX,TGO = FindDopplerMain.ephemerides(636391881,1)
# form the dataframe
occs = pd.DataFrame(ingresslist, columns=['Time'])
occs['Ingress'] = ingresslist
occs['Egress'] = egresslist

# Form the profile geometry to be ported into Cosmographia (see README)
Пример #12
0
def do_main(argv):

    sp.kclear()
    halfpi, dpr = sp.halfpi(), sp.dpr()

    fnck = 'out.bc'
    target = 'TEMPEL 1'
    frame = 'DIF_SPACECRAFT'
    for arg in argv:
        if arg.startswith('--ck='):
            fnck = arg[5:]
        elif arg.startswith('--target='):
            target = arg[9:]
        elif arg.startswith('--k='):
            sp.furnsh(arg[4:])
        else:
            assert False, 'Unknown argument [{0}]'.format(arg)

    frameid = sp.gipool('FRAME_{0}'.format(frame), 0, 1)[0]
    scid = sp.gipool('CK_{0}_SPK'.format(frameid), 0, 1)[0]
    scname = sp.bodc2s(scid, 99)
    cover = sp.stypes.SPICEDOUBLE_CELL(200)
    sp.scard(0, cover)
    cover = sp.ckcov(fnck, frameid, False, "INTERVAL", 0.0, "TDB")
    sp.furnsh(fnck)
    vbore = sp.vpack(-1, 0, 0)
    vorbitnorm = sp.vpack(0, 1, 0)
    for ipair in range(sp.wncard(cover)):
        et0, etend = sp.wnfetd(cover, 0)

        etlast, ettca, niter = et0, (et0 + etend) * 0.5, 0

        while ettca != etlast and niter < 20:
            etlast = ettca
            state6 = sp.spkezr(target, ettca, "j2000", "none", scname)[0]
            vpos, vvel = state6[:3], state6[3:]
            det = sp.vdot(vvel, vpos) / sp.vdot(vvel, vvel)
            ettca -= det
            niter += 1

        print(dict(
            TCAtdb=sp.etcal(ettca, 99),
            niter=niter,
        ))

        et = et0
        ets, boreerrs, orbitnormerrs = list(), list(), list()
        while et <= etend:
            state6 = sp.spkezr(target, et, frame, "none", scname)[0]
            vpos, vvel = state6[:3], state6[3:]
            ets.append(et - et0)
            boreerrs.append(dpr * sp.vsep(vbore, vpos))
            orbitnormerrs.append(dpr * (sp.vsep(vbore, vorbitnorm) - halfpi))
            et += max([0.01, 0.1 * abs(cos(sp.vsep(vpos, vvel)))])
        try:
            plt.plot(ets, orbitnormerrs, 'o', label='Orbit normal error')
            plt.plot(ets, boreerrs, 'o', label='Boresight error')
            plt.axvline(ettca - et0, label='TCA')
            plt.xlabel('Time, s past {0} TDB'.format(sp.etcal(et0, 99)))
            plt.ylabel('Error, deg')
            plt.legend(loc='best')
            plt.show()
        except:
            if do_debug:
                import traceback as tb
                tb.print_exc()
            print(boreerrs[::1000])
            print(orbitnormerrs[::1000])