Esempio n. 1
0
  def is_convex(self):

    try:
      assert hasattr(self,'convex')
      return self.convex
    except: pass

    assert self.fovtype == FOV.POLYGONTYPE, 'FOV.is_convex() method must called from polygon, not from {0}, FOV'.format(self.fovtype)

    uvuseds = list()
    uvlefts = self.uvfovxyzs[::]
    oneneg,onepos = False,False
    self.inwardsidenorms = list()

    norm = sp.ucrss(uvlefts[-1],uvlefts[0])
    for uv in uvlefts[1:-1]:
      dot = sp.vdot(norm,uv)
      if   0.0<dot: onepos = True
      elif 0.0>dot: oneneg = True
      else:
        self.convex = False
        return False

    if onepos: self.inwardsidenorms.append(norm)
    else     : self.inwardsidenorms.append(sp.vminus(norm))

    uv0 = uvlefts.pop()
    while uvlefts and (not (oneneg and onepos)):

      uv1 = uv0
      uv0 = uvlefts.pop()

      norm = sp.ucrss(uv0,uv1)

      for uv in (uvuseds+uvlefts):
        dot = sp.vdot(norm,uv)
        if   0.0<dot: onepos = True
        elif 0.0>dot: oneneg = True
        else:
          self.convex = False
          return False

      if onepos: self.inwardsidenorms.append(norm)
      else     : self.inwardsidenorms.append(sp.vminus(norm))

    self.convex = onepos ^ oneneg
    return self.convex
Esempio n. 2
0
  def __init__(self,fovraws,ralohi=(),declohi=()
              ,obs_pos=None,obs_vel=None,obs_year=None
              ):
    """Convert FOV definition in external inertial reference frame to an
FOV definition in a local reference frame; also determine RA,Dec limits
of FOV for use in star catalog lookup.

For polygonal FOVs, set up FOV as vectors in a local reference frame,
with a matrix to rotate vectors from the external to the local frame.
The local reference frame (reffrm) will have +Z along the average
vertices' direction, and will be rotated such the the +X axis is not
parallel to any of the edges of the polygon from the FOV projected onto
the Z=+1 plane.

Arguments

  fovraws - sequence either of vector and cone and half-angle for
            circular FOV, or of a pair of vectors for RA,Dec box, or
            of three or more vectors for a polygonal FOV.  A vector is
            a sequence either of two values, RA,Dec, or of three values,
            X,Y,Z.

  obs_pos - Observer position, solar system barycentric, 3-vector, km
            - For parallax correction

  obs_vel - Observer velocity, solar system barycentric, 3-vector, km/s
            - For proper motion correction

  obs_year - Observer time, y past 2015.5 (Gaia DR2 epoch)
            - For stellar aberration correction

"""
    ### Get count of items in FOV sequence; ensure it is 2 or more
    ### and ralohi and declohi are empty, or that fovraws is empty
    ### and ralohi and declohi have 2 values each
    (self.fovraws
    ,self.ralohi
    ,self.declohi
    ,self.obs_pos
    ,self.obs_vel
    ,self.obs_year
    ,)= fovraws,list(ralohi),list(declohi),obs_pos,obs_vel,obs_year
    self.L = len(fovraws)
    assert (1<self.L and not (self.ralohi+self.declohi)
      ) or (0==self.L and 2==len(self.ralohi) and 2==len(self.declohi)
      ), 'Invalid vertices in FOV'

    ################################
    ### Initialize:  FOV RA,Dec pairs; FOV type (assume polygon); FOV
    ###              vector triples; list of RA,Dec boxes
    self.radecdegs = list()
    self.fovtype = 1<self.L and FOV.POLYGONTYPE or FOV.RADECBOXTYPE
    self.uvfovxyzs,fovsum = list(),sp.vpack(0.,0.,0.)
    self.radec_boxes = list()
    rdba = self.radec_boxes.append   ### Shorthand to append box to list

    ################################
    ### Parse list of vertices:
    ### - [list,float]          =>  Circle (cone)
    ### - [list,list]           =>  RA,Dec box
    ### - [list,list,list,...]  =>  Polygon
    for vertex in fovraws:

      ### For second of two vertices ...
      if 1==len(self.radecdegs) and 2==self.L:
        ### Two-vertex items are either a conic FOV, or an [RA,Dec] box
        try:
          ### If second item in list is a float, then it's a half-angle
          ### of the cone
          self.hangdeg = float(vertex)
          assert self.hangdeg < 90.0,'Cone half-angle is not less than 90degrees'
          assert self.hangdeg > 0.0,'Cone half-angle is not greater than 0degrees'
          self.hangrad = self.hangdeg * rpd
          self.min_cosine = math.cos(self.hangrad)
          self.uv_cone_axis = self.uvfovxyzs[0]
          self.fovtype = FOV.CIRCLETYPE
          break
        except AssertionError as e:
          raise
        except:
          ### If the above fails, then it's the second corner of the box
          self.fovtype = FOV.RADECBOXTYPE

      ### Parse one vertex
      ra,dec,uvxyz = parse_inertial(vertex)

      ### Append RA,Dec and unit vector XYZ onto their resepective lists
      self.radecdegs.append((ra,dec,))
      self.uvfovxyzs.append(uvxyz)
      fovsum = sp.vadd(fovsum,uvxyz)

    ################################
    ### Calculate RA,DEC limits as list of [ralo,rahi,declo,dechi] boxes
    ### - .radec_boxes is a list; rdba is .radec_boxes.append
    ### - List will have multiple RA,Dec boxes if FOV crosses the Prime
    ###   Meridian (PM) an even number of times.

    if self.fovtype == FOV.RADECBOXTYPE:
      ### RA,DEC box FOV:  calculate limits; handle PM crossing
      if 2==self.L:
        ras,decs = zip(*self.radecdegs)
        ralo,rahi = sorted(ras)
        declo,dechi = sorted(decs)
        if 180 > (rahi-ralo):
          rdba([ralo,rahi,declo,dechi])
        else:
          rdba([0.0,ralo,declo,dechi])
          rdba([rahi,360.0,declo,dechi])
      else:
        if self.ralohi[1] > self.ralohi[0]:
          rdba(self.ralohi+self.declohi)
        else:
          rdba([self.ralohi[0],360.0]+self.declohi)
          rdba([0.0,self.ralohi[1]]+self.declohi)

    elif self.fovtype == FOV.CIRCLETYPE:
      ### Circular FOV:  DEC limits determine RA limits; handle PM Xing
      ra,dec = self.radecdegs[0]
      fovdeclo = dec - self.hangdeg
      fovdechi = dec + self.hangdeg

      if fovdeclo < -90.0 or fovdechi > 90.0:
        ### A pole is in the FOV; use full RA range
        fovralo,fovrahi = 0.0,360.0
        fovdeclo,fovdechi = max([fovdeclo,-90.0]),min([fovdechi,+90.0])

      elif fovdeclo == -90.0 or fovdechi == 90.0:
        ### A pole is on the FOV circumference; RA range is 180 degrees
        fovralo,fovrahi = ra-90.0,ra+90.0

      else:
        ### The FOV excludes the poles; calculate the RA range, using
        ### the formula validated in script validate_delta_ra_formula.py
        tanhang,tandec = math.tan(self.hangrad),math.tan(dec*rpd)
        sinhang,cosdec = math.sin(self.hangrad),math.cos(dec*rpd)
        coshang = math.cos(self.hangrad)
        T = sinhang / math.sqrt(1.0 - ((tanhang*tandec)**2))
        deltara = dpr * math.atan(T / (cosdec * coshang))
        fovralo,fovrahi = ra-deltara,ra+deltara

      ### Ensure RA limits are within range [0:360] (N.B. inclusive)
      if fovralo < 0.0: fovralo += 360.0
      if fovrahi > 360.0: fovrahi -= 360.0

      if fovralo <= fovrahi:
        ### RA lo <= RA hi:  no PM crosssing
        rdba([fovralo,fovrahi,fovdeclo,fovdechi])
      else:
        ### RA hi < RA hi:  there is a PM crosssing
        rdba([0.0,fovrahi,fovdeclo,fovdechi])
        rdba([fovralo,360.,fovdeclo,fovdechi])

    else:
      assert self.fovtype == FOV.POLYGONTYPE
      ### Polygonal FOV:  build frame where all vertices will be
      ### projected onto the plane Z=1

      ### .uvavg:  unit vector = mean of all vertices, will be +Z
      self.uvavg = sp.vhat(fovsum)

      ### Create rotation matrix to FOV frame:  +Z is mean of vertices'
      ###   directions (.uvavg); +X will be a direction that is not
      ###   parallel to any side of the polygon
      ### - Start with temporary matrix with +Z as defined above; +X
      ###   toward vertex at largest angle from .uvavg
      vother = min([(sp.vdot(self.uvavg,v),list(v),) for v in self.uvfovxyzs])[1]
      tmpmtx = sp.twovec(self.uvavg,3,vother,1)
      ### - Rotate all vectors to that frame; scale Z components to 1.0
      vtmps = list()
      for v in self.uvfovxyzs:
        ### - Ensure all vertices are in the same hemisphere
        assert 0.0 < sp.vdot(self.uvavg,v),'All vertices are not in the same hemisphere'
        vtmp = sp.mxv(tmpmtx,v)
        vtmps.append(sp.vscl(1.0/vtmp[2],vtmp))

      ### Find largest azimuth gap between any two sides:  that azimuth
      ###   will be direction of +X in the final rotation matrix
      ### - Get azimuths of all sides of polygon, in range [-PI:PI]
      azimuths,vlast = list(),vtmps[-1]
      for v in self.uvfovxyzs:
        azimuths.append(numpy.arctan((v[1]-vlast[1])/(v[0]-vlast[0])))
        vlast = v
      ### - Sort angles and add [least angle plus PI] to end of list
      azimuths.sort()
      azimuths.append(azimuths[0]+sp.pi())
      ### - Find largest delta-azimuth and its index
      dazimuths = [hi-lo for hi,lo in zip(azimuths[1:],azimuths[:-1])]
      maxdaz = max(dazimuths)
      imaxdaz = dazimuths.index(maxdaz)
      ### - Calculate azimuth from to mean of that delta-azimuth,
      meanaz = azimuths[imaxdaz] + (maxdaz / 2.0)

      ### Final matrix:  add rotation of tmpmtx around +Z by that angle
      self.mtxtofov = sp.mxm(sp.rotate(meanaz,3),tmpmtx)

      ### Apply final rotation matrix, store results in .uvlclxyzs
      tmpmtx = sp.twovec(self.uvavg,3,vother,1)
      self.uvlclxyzs = [self.rotate_to_local(v) for v in self.uvfovxyzs]

      ### Calculate upper and lower RA and Dec limits, with PM crossings
      los,his = list(),list()
      ### - Create [[RA,Dec],[X,Y,Z]] pairs list; ensure last is off PM
      pairs = list(zip(self.radecdegs,self.uvfovxyzs))
      pop_count = 0
      while pairs[-1][0][0] == 0.0:
        pop_count += 1
        assert pop_count < self.L,'All vertices are on the Prime Meridian'
        pairs.append(pairs.pop(0))

      ### Count PM crossings
      self.crossing_count = 0
      lastra = pairs[-1][0][0]
      zero_count = 0
      for (ra,dec,),xyz in pairs:
        if ra == 0.0:
          zero_count += 1
          if lastra > 180.0: ra = 360.0
        if 180 < abs(ra-lastra): self.crossing_count += 1
        lastra = ra

      if 0==self.crossing_count or 1==(1&self.crossing_count):
        ### If there are either no, or an odd number, of PM crossings,
        ### then use the pairs as-is for a single FOV
        subfovs = [pairs]
        if self.crossing_count:
          ### - For odd crossing count, one pole or the other must be
          ###   in the FOV; init full RA range, that pole for Dec ranges
          ralo,rahi = 0.0,360.0
          if sp.vdot(self.uvavg,[0,0,1]) > 0.0: declo = dechi = +90.0
          else                                : declo = dechi = -90.0
        else:
          ### - For zero crossing count, initialize inverted ranges
          ralo,rahi = 360.0,0.0
          declo,dechi = +90.0,-90.0
        subranges = [[ralo,rahi,declo,dechi]]

      else:
        ### If there are an even, non-zero number of PM crossings, break
        ### them into two sub-FOVs, one on either side of the PM

        eastfov,westfov = list(),list()

        if zero_count:
          ### If there are any zero RA values, rotate the pairs to
          ### ensure a zero-RA pair is the first, so it and the non-zero
          ### last pair will be assigned to the correct side of the PM
          while pairs[0][0][0]!=0.0: pairs.append(pairs.pop(0))
        else:
          ### If there are no zero RA values, rotate the pairs to ensure
          ### a crossing occurs between the last and first pair, so the
          ### corresponding zero crossing will be assigned to the
          ### correct side of the PM
          while abs(pairs[0][0][0]-pairs[-1][0][0])<180:
            pairs.append(pairs.pop(0))

        ### Write vertices into the two sub-FOVs

        ### - Set last-vertex values for first item in pairs
        (lastra,lastdec,),lastxyz = pairs[-1]

        for pair in pairs:
          ### - Loop over vertex pairs ((RA,DEC,),Cartesian_Vector)
          (ra,dec,),xyz = pair

          if ra == 0.0:

            ### - When RA=0, the previous RA determines if it's 0 ar 360
            if lastra >= 180.0:
              ra = 360.0
              westfov.append([(ra,dec,),xyz])
              iswest = True
            else:
              eastfov.append(pair)
              iswest = False

          elif abs(lastra-ra) >= 180.0:

            ### - When the change in RA>=180, the PM is being crossed

            ### - Find the mid-vector where the PM is crossed
            k1 = -xyz[1] / (lastxyz[1]-xyz[1])
            midxyz = sp.vhat(sp.vlcom(1.0-k1,xyz,k1,lastxyz))
            middec = dpr * sp.recrad(midxyz)[2]

            ### - Add that mid-vector, with RA=360, to the west FOV
            westfov.append([(360.0,middec,),midxyz])

            ### - Determine if vector is west
            iswest = ra >= 180.0

            ### - Add that mid-vector, with RA=0, to the east FOV ...
            if (ra > 0.0) and (not iswest):
              ### - ... only if the ra is not already 0, as it will be
              ###       added in the next step
              eastfov.append([(0.0,middec,),midxyz])

            ### Add the vector to either east or west FOV
            if iswest: westfov.append(pair)
            else     : eastfov.append(pair)

          else:

            ### PM was not crossed, add vector to same FOV, as last time
            if iswest: westfov.append(pair)
            else     : eastfov.append(pair)

          ### - Set last-vertex values for next item in pairs
          (lastra,lastdec,),lastxyz = (ra,dec,),xyz

        ### - Create subfovs list of east and west FOVs; set subranges
        subfovs = [eastfov,westfov]
        subranges = [[360.0,0.0,90.0,-90.0],[360.0,0.0,90.0,-90.0]]

      ### To here, we have list of FOV(s) and list of range(s); use them
      ### to determine RA,DEC box(es) to use for database query

      while subfovs:

        ### Get sub-FOV, sub-range; set last vertex's XYZ
        subfov,(ralo,rahi,declo,dechi,) = subfovs.pop(),subranges.pop()
        lastxyz = subfov[-1][-1]

        for pair in subfov:
          ### Each element of subfov comprises (RA,Dec) and vertex XYZ
          ### - xyz is a unit vector
          (ra,dec,),xyz = pair

          ### - Adjust RA limits as needed from RA of vertex
          if   ra > rahi: rahi = ra
          elif ra < ralo: ralo = ra

          ### - Set Dec extrema from DEC of vertex
          maxdec = mindec = dec

          ### - Calculate Dec extrema from lastxyz to xyz
          ### -- Normal to plane of lastxyz and syz
          sidenormal = sp.vcrss(lastxyz,xyz)
          ### -- Z-rates along great circle at lastxyz and at xyz
          lastdz = sp.vcrss(sidenormal,lastxyz)[2]
          dz = sp.vcrss(sidenormal,xyz)[2]
          if 0.0 > (lastdz*dz):
            ### -- If sign of Z-rates differs, there should be an
            ###    extreme value between lastxyz and xyz
            ### --- Get vector perpendicular to side normal on equator
            ### --- Use that to calculate the unit vector at Dec extreme
            equinox = sp.vcrss([0,0,1],sidenormal)
            vtoextremez = sp.ucrss(sidenormal,equinox)
            ### --- Cosine of angle between lastxyz and xyz
            mindot = sp.vdot(lastxyz,xyz)
            for none in [None,None]:
              ### --- Two cases:  vtoextremez and -vtoextremez
              ###     - Angles from vtoextremez to lastxyz and to xyz
              ###       must be less than angle between lastxyz and xyz
              ###       so cosines of those angles must be greater
              lastxyzdot = sp.vdot(lastxyz,vtoextremez)
              xyzdot = sp.vdot(xyz,vtoextremez)
              if lastxyzdot>mindot and xyzdot>mindot:
                ### --- Adjust maxdec and mindec as needed
                try   : extremedec = dpr * math.asin(vtoextremez[2])
                except: extremedec = dpr * sp.recrad(vtoextremez)[2]
                if   extremedec > maxdec: maxdec = extremedec
                elif extremedec < mindec: mindec = extremedec
                break
              ### --- Invert vtoextremez for next pass
              vtoextremez = sp.vminus(vtoextremez)

          ### - Adjust Dec limits as needed from Dec extrema of side
          if maxdec > dechi: dechi = maxdec
          if mindec < declo: declo = mindec
          lastxyz = xyz

        ### Append calculated RA,Dec box(es)
        rdba((ralo,rahi,declo,dechi,))

      ### Put None in .localxyzs, in .v_for_stellar_aberr, and in
      ### .v_for_parallax; if no stellar aberration or parallax is
      ### explicitly applied to define it later, then .localxyzs will be
      ### calculated on the fly
      self.localxyzs = None
      self.v_for_stellar_aberr = None
      self.v_for_parallax = None
Esempio n. 3
0
  def star_in_fov(self
                 ,vstar
                 ,parallax_maspau=None
                 ,pmra_maspy=None
                 ,pmdec_maspy=None
                 ):
    """Return True if the star (RA,Dec or xyz) argument is in the FOV

Argument vstar is either an RA,Dec pair (degrees) or a 3-vector

"""

    if self.fovtype == FOV.RADECBOXTYPE:
      ##################################################################
      ### Compare star vector to [RA,Dec] box
      ra,dec = parse_inertial(vstar,return_radec=True)[:2]
      for ralo,rahi,declo,dechi in self.radec_boxes:
        if ra<ralo: continue
        if ra>rahi: continue
        if dec<declo: continue
        if dec>dechi: continue
        return True,sp.radrec(1.,rpd*ra,rpd*dec)
      return False,None

    ### Get inertial star unit vector without RA,Dec
    uvinertial = uvraw = parse_inertial(vstar,return_radec=False)

    ### Corrections for direction to star
    ### - Assume all corrections are small and can be applied in units
    ###   of radians to a unit vector in a plane perpendicular to the
    ###   vector to the star

    ### - Proper Motion (PM)
    ###   - Uses PM in RA and Dec only, not radial velocity and parallax
    if (not (None is self.obs_year)
       ) and (not (None in (pmra_maspy,pmdec_maspy,))
       ) and (pmra_maspy != 0.0 or pmdec_maspy != 0.0):
      ### - Unit vectors E and N in plane perpendicular to star vector
      uveast = sp.ucrss([0,0,1],uvraw)
      uvnorth = sp.ucrss(uvraw,uveast)
      ### - Scale unit vectors in radians, and add to nominal vector
      ### - pmra_maspy from Gaia includes factor of secant(Declination)
      uvinertial = sp.vhat(sp.vlcom3(self.obs_year*rpmas*pmdec_maspy,uvnorth
                                    ,self.obs_year*rpmas*pmra_maspy,uveast
                                    ,1.0,uvinertial
                                    )
                          )

    ### - Parallax
    if (not (None is self.obs_pos)
       ) and (not (None is parallax_maspau)
       ) and (parallax_maspau != 0.0):
      ### - Scale observer position, by parallax in mas/AU, then scale
      ###   to radians (since star vector is unit vector), make that the
      ###   new origin of the vector
      uvinertial = sp.vhat(sp.vsub(uvinertial
                                  ,sp.vscl(aupkm*parallax_maspau*rpmas,self.obs_pos)
                                  )
                          )

    ### - Stellar Aberration
    if not (None is self.obs_vel):
      ### - Scale observer velocity by reciprocal of the speed of light,
      ###   add result to unit vector toward star.
      uvinertial = sp.vhat(sp.vadd(uvinertial
                                  ,sp.vscl(recip_clight,self.obs_vel)
                                  )
                          )

    if self.fovtype == FOV.CIRCLETYPE:
      ##################################################################
      ### Compare inertial star vector to circular FOV
      return sp.vdot(uvinertial,self.uv_cone_axis) >= self.min_cosine,uvinertial

    assert FOV.POLYGONTYPE == self.fovtype,'Unknown FOV type [{0}]'.format(self.fovtype)

    ####################################################################
    ### Compare star vector to polygonal FOV

    if self.is_convex():
      ### Convex FOV:  a negative dot product with the inward-pointing
      ###              normal to any side indicates star is outside FOV
      for inwardnorm in self.inwardsidenorms:
        if sp.vdot(uvinertial,inwardnorm) < 0.0: return False,uvinertial

      ### All dot products were non-negative:  star is within FOV
      return True,uvinertial

    ### Rotate inertial unit vector to local reference frame (reffrm)
    uvlocalstar = self.rotate_to_local(uvinertial)

    ### Scale to Z=unity
    if uvlocalstar[2] < 1e-15: return False,uvinertial
    z1star = sp.vscl(1.0/uvlocalstar[2],uvlocalstar)

    ### Setup .localxyzs and .fovsides
    if None is self.localxyzs: self.setup_localxyzs()

    ### Count number of crossings of FOV sides
    count = len([None
                 for fovside in self.fovsides
                 if fovside.right_of(z1star)
                ])

    return ((count&1) and True or False),uvinertial
Esempio n. 4
0
                ### ORX to 9101955; 9101955 to Bennu.
                vBennu, ltBennu = sp.spkpos('BENNU', et, 'ORX_SPACECRAFT',
                                            'NONE', 'ORX')
                vIsh, ltIsh = sp.spkpos('9101955', et, 'ORX_SPACECRAFT',
                                        'NONE', 'ORX')
                vBennuIsh, ltBennuIsh = sp.spkpos('BENNU', et, 'J2000', 'NONE',
                                                  '9101955')

                ### Limit roundoff error, not needed for proxops (time range
                ### starting at 2018-337)
                ### assert (ltBennu * clight) < 100000

                ### Law of cosines calculation of Bennu-9101955 distatnce
                dLocs.append(
                    math.sqrt(
                        sp.vdot(vBennu, vBennu) + sp.vdot(vIsh, vIsh) -
                        (2 * sp.vdot(vBennu, vIsh))))

                offsets.append(ltBennuIsh * clight)

            except AssertionError:
                pass
            except sp.stypes.SpiceyError:
                if doDebug:
                    import traceback as tb
                    tb.print_exc()

            et += spd / 4

        ### Tests:
Esempio n. 5
0
def norm2surfpt(semi_axes, inputNormal):

    ### Ellipsoid semi-axes' lengths, per the ellipsoid formula:
    ###
    ###        2           2           2
    ###   / x \       / y \       / z \
    ###  ( --- )  +  ( --- )  +  ( --- ) =  1
    ###   \ a /       \ b /       \ c /

    ### Ensure abc components are positive
    abc = sp.vequ([abs(semi_axis) for semi_axis in semi_axes[:3]])

    ### Get unit vector, n, parallel to input normal
    n = sp.vhat(inputNormal)

    ### Direction of normal, N, at [x,y,z] is [ x/(a*a), y/(b*b), z/(c*c)]
    ### - Vector N is unknown, and is not necessarily a unit vector
    ### - Argument inputNormal is parallel to N, and of arbitrary length
    ### - Unit vector parallel to N is n, calculated above from inputNormal
    ### - Assume length of N is scalar 1/k; k is initially unknown
    ### - Scaling unit normal, n, by k yields N:
    ###
    ###     n/k = N = [x/(a*a), y/(b*b), z/(c*c)]      Eqn. 1
    ###
    ### so
    ###
    ###     nx/k = x/(a*a)                                    Eqn. 2x
    ###     ny/k = y/(b*b)                                    Eqn. 2y
    ###     nz/k = z/(c*c)                                    Eqn. 2z
    ###
    ### and, solving for surface point components, [x,y,z]:
    ###
    ###     x = nx*a*a/k                                      Eqn. 3x
    ###     y = ny*b*b/k                                      Eqn. 3y
    ###     z = nz*c*c/k                                      Eqn. 3z
    ###
    ### Substituting Eqns. 3x, 3y, and 3z for x, y, and z
    ### back into the ellipsoid formula (x^2/a^2 + ... = 1):
    ###
    ###     (nx*a*a/k)^2/(a*a)
    ###   + (ny*b*b/k)^2/(b*b)
    ###   + (nz*c*c/k)^2/(c*c) = 1                            Eqn. 4
    ###
    ### and solving for k:
    ###
    ###     (nx*a)^2 + (ny*b)^2 + (nz*c)^2 = k^2              Eqn. 5
    ###
    ### Since nx, a, ny, b, nz, and c are all known, k
    ### can be calculated directly using Eqn. 5, and the
    ### surface point components, x, y, and z, can then
    ### be calculated using Eqns. 3x, 3y, and 3z.

    ### Calculate two vectors:
    ### - [a*a, b*b, c*c]
    ### - [nx*nx, ny*ny, nz*nz]

    abcXabc = vXv(abc, abc)
    nXn = vXv(n, n)

    ### Solve for k using abcXabc, nXn, and Eqn. 5:

    k2 = sp.vdot(abcXabc, nXn)
    k = math.sqrt(k2)

    ### Use k, abcXabc, n, and Eqns. 3x, 3y, and
    ### 3z to calculate surface point vector xyz

    xyz = sp.vscl(1. / k, vXv(abcXabc, n))

    ### Debug logging:
    if doLog:
        ### Calculate (x/a)^2 + (y/b)^2 + (z/c)^2; it should be = 1
        one = sp.vdot(vXv(xyz, xyz), 1 / abcXabc)
        pprint.pprint(
            dict(n=n,
                 abc=abc,
                 abcXabc=abcXabc,
                 nXn=nXn,
                 nMag=sp.vnorm(n),
                 k=k,
                 k2=k2,
                 xyz=xyz,
                 one=one))

    ### Return surface point

    return xyz
Esempio n. 6
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])
Esempio n. 7
0
  for iet in range(-400,401):

    ### ET (TDB)
    et = float(iet) / 10
    ets.append(et)

    ### Position of Bennu (2101955) wrt instrument
    storxinstr,ltorxinstr = sp.spkezr('2101955',et,'J2000','none','-64999')
    storx,ltorx = sp.spkezr('2101955',et,'J2000','none','-64')

    ### Position of Bennu (2101955) wrt S/C COM
    porxinstr,vorxinstr = storxinstr[:3],storxinstr[3:]
    porx,vorx = storx[:3],storx[3:]

    ### Delta dRange/dt
    dvs.append( sp.vdot(vorxinstr,sp.vhat(porx))
              - sp.vdot(vorx,sp.vhat(porx))
              )

    porxinstrs.append(list(porxinstr))
    vorxinstrs.append(list(vorxinstr))
    porxs.append(list(porx))
    vorxs.append(list(vorx))

  with open('relvel.json','w') as fjson:
    sj.dump(dict(porxinstrs=porxinstrs
                ,vorxinstrs=vorxinstrs
                ,porxs=porxs
                ,vorxs=vorxs
                ,dvs=dvs
                ,ets=ets
Esempio n. 8
0
def get_illuminated_shape(probe: str, body: str, time: datetime,
                          angular_unit: str) -> Polygon:
    """ Calculates the shape of sun-illuminated part of SPICE body as viewed from a probe.

    :param probe: Name of probe, e.g. "JUICE"
    :param body: Name of body, e.g. "CALLISTO"
    :param time: Time of observation
    :param angular_unit: Angular unit, one of ["deg", "rad", "arcMin", "arcSec"]
    :return: Polygon marking the illuminated part of body as viewed from probe, centered on the nadir
             point. The x-direction points towards the Sun.
    """
    if angular_unit not in angular_units:
        raise ValueError(
            f"Unknown angular_unit: '{angular_unit}'. Allowed units: {angular_units}"
        )

    et = datetime2et(time)
    ncuts = 20

    # we need to compute our own coordinate system, where +z is the probe->body vector,
    # the Sun lies in the x-z plane, with +x direction towards the Sun
    # this coordinate system is left-handed (from view of probe, +z points into the screen,
    # +x points right, and +y points up)
    sun_position_from_probe = spy.spkpos("SUN", et, f"IAU_{body}", "LT+S",
                                         probe)[0]
    body_position_from_probe = spy.spkpos(body, et, f"IAU_{body}", "LT+S",
                                          probe)[0]

    # we only need to know the orientations os x-z and y-z planes, we don't care about point of origin
    x_z_plane_normal_vector = spy.vcrss(sun_position_from_probe,
                                        body_position_from_probe)
    y_z_plane_normal_vector = spy.vcrss(body_position_from_probe,
                                        x_z_plane_normal_vector)

    # the illuminated side of limb in our coordinate system is always on the right side, so we start
    # with +y direction (x_z_plane_normal_vector), and rotate clockwise for 180 degrees
    step_limb = -np.pi / ncuts
    limb_points = spy.limbpt("TANGENT/ELLIPSOID", body, et, f"IAU_{body}",
                             "LT+S", "CENTER", probe, x_z_plane_normal_vector,
                             step_limb, ncuts, 1.0, 1.0, ncuts)[3]
    assert (len(limb_points == ncuts))

    # if we preserve the upwards direction of y axis, but look at the body from POV of the Sun,
    # the probe will always be on the left side. That means we start slicing again at +y direction,
    # but this time rotate counter-clockwise for 180 degrees, to get terminator points that are
    # actually visible from probe
    step_terminator = np.pi / ncuts
    terminator_points = spy.termpt("UMBRAL/TANGENT/ELLIPSOID", "SUN", body, et,
                                   f"IAU_{body}", "LT+S", "CENTER", probe,
                                   x_z_plane_normal_vector, step_terminator,
                                   ncuts, 1.0, 1.0, ncuts)[3]
    assert (len(terminator_points <= ncuts))

    # reverse the order of terminator points so we go
    # (limb top -> ... -> limb bottom -> terminator bottom -> ... -> terminator top)
    # these vectors emanate from probe towards the body limb and terminator
    points_3d = list(limb_points) + list(reversed(terminator_points))

    # project the points from IAU body-fixed 3d frame, to our probe POV 2d frame
    points_2d = []
    for p in points_3d:
        # x-coordinate of each point is the angle vector with the y-z plane (this also recognizes the sign)
        x_rad = np.arcsin(
            spy.vdot(y_z_plane_normal_vector, p) /
            (spy.vnorm(y_z_plane_normal_vector) * spy.vnorm(p)))
        # similarly, y-coordinate is angle with x-z plane
        y_rad = np.arcsin(
            spy.vdot(x_z_plane_normal_vector, p) /
            (spy.vnorm(x_z_plane_normal_vector) * spy.vnorm(p)))
        # convert the angular coordinate from radians to desired unit, and add to list
        points_2d.append((convertAngleFromTo(x_rad, "rad", angular_unit),
                          convertAngleFromTo(y_rad, "rad", angular_unit)))
    return Polygon(points_2d)