def __init__(self, SCP, ARPPos, ARPVel): self.SCP = SCP self.ARP = ARPPos self.ARP_vel = ARPVel self.LOS = (self.SCP - self.ARP) # unit vector versions self.uSCP = self._make_unit(self.SCP) self.uARP = self._make_unit(self.ARP) self.uARP_vel = self._make_unit(self.ARP_vel) self.uLOS = self._make_unit(self.LOS) self.left = numpy.cross(self.uARP, self.uARP_vel) self.look = numpy.sign(self.left.dot(self.uLOS)) # Earth Tangent Plane (ETP) at the SCP is the plane tangent to the surface of constant height # above the WGS 84 ellipsoid (HAE) that contains the SCP. The ETP is an approximation to the # ground plane at the SCP. self.ETP = geocoords.wgs_84_norm(SCP) # slant plane unit normal self.uSPZ = self._make_unit(self.look * numpy.cross(self.ARP_vel, self.uLOS)) # perpendicular component of range vector wrt the ground plane self.uGPX = self._make_unit(-self.uLOS + numpy.dot(self.ETP, self.uLOS) * self.ETP) self.uGPY = numpy.cross(self.ETP, self.uGPX) # already unit vector # perpendicular component of north wrt the ground plane self.uNORTH = self._make_unit( numpy.array([0, 0, 1]) - self.ETP[2] * self.ETP) self.uEAST = numpy.cross(self.uNORTH, self.ETP) # already unit vector
def __init__(self, geometry_calculator, row_vector, col_vector): """ Parameters ---------- geometry_calculator : GeometryCalculator row_vector : numpy.ndarray col_vector : numpy.ndarray """ # extract sicd based parameters self.geometry_calculator = geometry_calculator self.ARPPos = geometry_calculator.ARP self.ARPVel = geometry_calculator.ARP_vel self.SCP = geometry_calculator.SCP self.slant_x = self._make_unit(self.ARPPos - self.SCP) self.slant_z = self._make_unit(numpy.cross(self.slant_x, self.ARPVel)) if self.slant_z.dot(self.ARPPos) < 0: self.slant_z *= -1 self.slant_y = self._make_unit(numpy.cross(self.slant_z, self.slant_x)) self.ETP = wgs_84_norm(self.SCP) # store SIDD grid parameters self.row_vector = self._make_unit(row_vector) self.col_vector = self._make_unit(col_vector) self.normal_vector = self._make_unit( numpy.cross(self.row_vector, self.col_vector))
def __init__(self, sicd, row_vector, col_vector): """ Parameters ---------- sicd : SICDType row_vector : numpy.ndarray col_vector : numpy.ndarray """ # extract sicd based parameters self.sicd = sicd self.ARPPos = sicd.SCPCOA.ARPPos.get_array() self.ARPVel = sicd.SCPCOA.ARPVel.get_array() self.SCP = sicd.GeoData.SCP.ECF.get_array() self.slant_x = self._make_unit(self.ARPPos - self.SCP) self.slant_z = self._make_unit(numpy.cross(self.slant_x, self.ARPVel)) if self.slant_z.dot(self.ARPPos) < 0: self.slant_z *= -1 self.slant_y = self._make_unit(numpy.cross(self.slant_z, self.slant_x)) self.ETP = wgs_84_norm(self.SCP) # store SIDD grid parameters self.row_vector = self._make_unit(row_vector) self.col_vector = self._make_unit(col_vector) self.normal_vector = self._make_unit( numpy.cross(self.row_vector, self.col_vector))
def _derive_parameters(self, Grid, SCPCOA, GeoData): """ Expected to be called from SICD parent. Parameters ---------- Grid : sarpy.io.complex.sicd_elements.GridType SCPCOA : sarpy.io.complex.sicd_elements.SCPCOA.SCPCOAType GeoData : sarpy.io.complex.sicd_elements.GeoData.GeoDataType Returns ------- None """ if self.PolarAngRefTime is None and SCPCOA.SCPTime is not None: self.PolarAngRefTime = SCPCOA.SCPTime if GeoData is not None and GeoData.SCP is not None and GeoData.SCP.ECF is not None and \ SCPCOA.ARPPos is not None and SCPCOA.ARPVel is not None: SCP = GeoData.SCP.ECF.get_array() ETP = geocoords.wgs_84_norm(SCP) ARP = SCPCOA.ARPPos.get_array() LOS = (SCP - ARP) uLOS = LOS / norm(LOS) look = SCPCOA.look ARP_vel = SCPCOA.ARPVel.get_array() uSPZ = look * numpy.cross(ARP_vel, uLOS) uSPZ /= norm(uSPZ) if Grid is not None and Grid.ImagePlane is not None: if self.IPN is None: if Grid.ImagePlane == 'SLANT': self.IPN = XYZType.from_array(uSPZ) elif Grid.ImagePlane == 'GROUND': self.IPN = XYZType.from_array(ETP) elif self.IPN is None: self.IPN = XYZType.from_array( uSPZ) # assuming slant -> most common if self.FPN is None: self.FPN = XYZType.from_array(ETP)
def _derive_geometry_parameters(self, GeoData): """ Expected to be called by SICD parent. Parameters ---------- GeoData : sarpy.io.complex.sicd_elements.GeoData.GeoDataType Returns ------- None """ if GeoData is None or GeoData.SCP is None or GeoData.SCP.ECF is None or \ self.ARPPos is None or self.ARPVel is None: return # nothing can be derived SCP = GeoData.SCP.ECF.get_array() ARP = self.ARPPos.get_array() ARP_vel = self.ARPVel.get_array() LOS = (SCP - ARP) # unit vector versions uSCP = SCP/norm(SCP) uARP = ARP/norm(ARP) uARP_vel = ARP_vel/norm(ARP_vel) uLOS = LOS/norm(LOS) # cross product junk left = numpy.cross(uARP, uARP_vel) look = numpy.sign(numpy.dot(left, uLOS)) sot = 'R' if look < 0 else 'L' if self.SideOfTrack is None: self.SideOfTrack = sot elif self.SideOfTrack != sot: logging.error( 'In SCPCOAType, the derived value for SideOfTrack is {} and the set ' 'value is {}'.format(sot, self.SideOfTrack)) slant_range = numpy.linalg.norm(LOS) if self.SlantRange is None: self.SlantRange = slant_range elif abs(self.SlantRange - slant_range) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for SlantRange is {} and the set ' 'value is {}'.format(slant_range, self.SlantRange)) ground_range = norm(SCP)*numpy.arccos(numpy.dot(uSCP, uARP)) if self.GroundRange is None: self.GroundRange = ground_range elif abs(self.GroundRange - ground_range) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for GroundRange is {} and the set ' 'value is {}'.format(ground_range, self.GroundRange)) doppler_cone = numpy.rad2deg(numpy.arccos(numpy.dot(uARP_vel, uLOS))) if self.DopplerConeAng is None: self.DopplerConeAng = doppler_cone elif abs(self.DopplerConeAng - doppler_cone) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for DopplerConeAng is {} and the set ' 'value is {}'.format(doppler_cone, self.DopplerConeAng)) # Earth Tangent Plane (ETP) at the SCP is the plane tangent to the surface of constant height # above the WGS 84 ellipsoid (HAE) that contains the SCP. The ETP is an approximation to the # ground plane at the SCP. ETP = geocoords.wgs_84_norm(SCP) graze_ang = numpy.rad2deg(numpy.arcsin(numpy.dot(ETP, -uLOS))) if self.GrazeAng is None: self.GrazeAng = graze_ang elif abs(self.GrazeAng - graze_ang) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for GrazeAng is {} and the set ' 'value is {}'.format(graze_ang, self.GrazeAng)) if self.IncidenceAng is None: self.IncidenceAng = 90 - self.GrazeAng # slant plane unit normal uSPZ = look*numpy.cross(ARP_vel, uLOS) uSPZ /= norm(uSPZ) # perpendicular component of range vector wrt the ground plane uGPX = -uLOS + numpy.dot(ETP, uLOS)*ETP uGPX /= norm(uGPX) uGPY = numpy.cross(ETP, uGPX) # already unit vector twist_ang = -numpy.rad2deg(numpy.arcsin(numpy.dot(uGPY, uSPZ))) if self.TwistAng is None: self.TwistAng = twist_ang elif abs(self.TwistAng - twist_ang) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for TwistAng is {} and the set ' 'value is {}'.format(twist_ang, self.TwistAng)) slope_ang = numpy.rad2deg(numpy.arccos(numpy.dot(ETP, uSPZ))) if self.SlopeAng is None: self.SlopeAng = slope_ang elif abs(self.SlopeAng - slope_ang) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for SlopeAng is {} and the set ' 'value is {}'.format(slope_ang, self.SlopeAng)) # perpendicular component of north wrt the ground plane NORTH = numpy.array([0, 0, 1]) - ETP[2]*ETP uNORTH = NORTH/norm(NORTH) uEAST = numpy.cross(uNORTH, ETP) # already unit vector azim_ang = numpy.rad2deg(numpy.arctan2(numpy.dot(uGPX, uEAST), numpy.dot(uGPX, uNORTH))) azim_ang = azim_ang if azim_ang > 0 else azim_ang + 360 if self.AzimAng is None: self.AzimAng = azim_ang elif abs(self.AzimAng - azim_ang) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for AzimAng is {} and the set ' 'value is {}'.format(azim_ang, self.AzimAng)) # perpendicular component of ground plane wrt slant plane layover_ground = ETP - numpy.dot(ETP, uSPZ)*uSPZ # TODO: reciprocal in sicd.py line 1605? layover_ang = numpy.rad2deg(numpy.arctan2(numpy.dot(layover_ground, uEAST), numpy.dot(layover_ground, uNORTH))) layover_ang = layover_ang if layover_ang > 0 else layover_ang + 360 if self.LayoverAng is None: self.LayoverAng = layover_ang elif abs(self.LayoverAng - layover_ang) > 1e-5: # sensible tolerance? logging.error('In SCPCOAType, the derived value for LayoverAng is {} and the set ' 'value is {}'.format(layover_ang, self.LayoverAng))
def set_plane_frame(self, normal_vector=None, row_vector=None, col_vector=None): """ Set the plane unit normal, and the row and column vectors, in ECF coordinates. Note that the perpendicular component of col_vector with respect to the row_vector will be used. If `normal_vector`, `row_vector`, and `col_vector` are all `None`, then the normal to the Earth tangent plane at the reference point is used for `normal_vector`. The `row_vector` will be defined as the perpendicular component of `sicd.Grid.Row.UVectECF` to `normal_vector`. The `colummn_vector` will be defined as the component of `sicd.Grid.Col.UVectECF` perpendicular to both `normal_vector` and `row_vector`. If only `normal_vector` is supplied, then the `row_vector` and `column_vector` will be defined similarly as the perpendicular components of `sicd.Grid.Row.UVectECF` and `sicd.Grid.Col.UVectECF`. Otherwise, all vectors supplied will be normalized, but are required to be mutually perpendicular. If only two vectors are supplied, then the third will be determined. Parameters ---------- normal_vector : None|numpy.ndarray The vector defining the outward unit normal in ECF coordinates. row_vector : None|numpy.ndarray The vector defining increasing column direction. col_vector : None|numpy.ndarray The vector defining increasing column direction. Returns ------- None """ def normalize(vec, name, perp=None): if not isinstance(vec, numpy.ndarray): vec = numpy.array(vec, dtype=numpy.float64) if not (isinstance(vec, numpy.ndarray) and vec.ndim == 1 and vec.size == 3): raise ValueError( '{} vector must be a numpy.ndarray of dimension 1 and size 3.' .format(name)) vec = numpy.copy(vec) if perp is None: pass elif isinstance(perp, numpy.ndarray): vec = vec - perp * (perp.dot(vec)) else: for entry in perp: vec = vec - entry * (entry.dot(vec)) norm = numpy.linalg.norm(vec) if norm == 0: raise ValueError( '{} vector cannot be the zero vector.'.format(name)) elif norm != 1: vec = vec / norm # avoid modifying row_vector def exterior to this class return vec def check_perp(vec1, vec2, name1, name2, tolerance=1e-6): if abs(vec1.dot(vec2)) > tolerance: raise ValueError( '{} vector and {} vector are not perpendicular'.format( name1, name2)) if self._reference_point is None: raise ValueError( 'This requires that reference point is previously set.') if normal_vector is None and row_vector is None and col_vector is None: if self.sicd.RadarCollection.Area is None: self._normal_vector = wgs_84_norm(self.reference_point) self._row_vector = normalize( self.sicd.Grid.Row.UVectECF.get_array(), 'row', perp=self.normal_vector) self._col_vector = normalize( self.sicd.Grid.Col.UVectECF.get_array(), 'column', perp=(self.normal_vector, self.row_vector)) else: self._row_vector = self.sicd.RadarCollection.Area.Plane.XDir.UVectECF.get_array( ) self._col_vector = normalize(self.sicd.RadarCollection.Area. Plane.YDir.UVectECF.get_array(), 'col', perp=self._row_vector) self._normal_vector = numpy.cross(self._row_vector, self._col_vector) elif normal_vector is not None and row_vector is None and col_vector is None: self._normal_vector = normalize(normal_vector, 'normal') self._row_vector = normalize( self.sicd.Grid.Row.UVectECF.get_array(), 'row', perp=self.normal_vector) self._col_vector = normalize( self.sicd.Grid.Col.UVectECF.get_array(), 'column', perp=(self.normal_vector, self.row_vector)) elif normal_vector is None: if row_vector is None or col_vector is None: raise ValueError( 'normal_vector is not defined, so both row_vector and col_vector must be.' ) row_vector = normalize(row_vector, 'row') col_vector = normalize(col_vector, 'col') check_perp(row_vector, col_vector, 'row', 'col') self._row_vector = row_vector self._col_vector = col_vector self._normal_vector = numpy.cross(row_vector, col_vector) elif col_vector is None: if row_vector is None: raise ValueError( 'col_vector is not defined, so both normal_vector and row_vector must be.' ) normal_vector = normalize(normal_vector, 'normal') row_vector = normalize(row_vector, 'row') check_perp(normal_vector, row_vector, 'normal', 'row') self._normal_vector = normal_vector self._row_vector = row_vector self._col_vector = numpy.cross(self.normal_vector, self.row_vector) elif row_vector is None: normal_vector = normalize(normal_vector, 'normal') col_vector = normalize(col_vector, 'col') check_perp(normal_vector, col_vector, 'normal', 'col') self._normal_vector = normal_vector self._col_vector = col_vector self._row_vector = numpy.cross(self.col_vector, self.normal_vector) else: normal_vector = normalize(normal_vector, 'normal') row_vector = normalize(row_vector, 'row') col_vector = normalize(col_vector, 'col') check_perp(normal_vector, row_vector, 'normal', 'row') check_perp(normal_vector, col_vector, 'normal', 'col') check_perp(row_vector, col_vector, 'row', 'col') self._normal_vector = normal_vector self._row_vector = row_vector self._col_vector = col_vector # check for outward unit norm if numpy.dot(self.normal_vector, self.reference_point) < 0: logger.warning( 'The normal vector appears to be outward pointing, so reversing.' ) self._normal_vector *= -1
def _derive_parameters(self, Grid, SCPCOA, GeoData, Position, Timeline): """ Expected to be called from SICD parent. Parameters ---------- Grid : sarpy.io.complex.sicd_elements.Grid.GridType SCPCOA : sarpy.io.complex.sicd_elements.SCPCOA.SCPCOAType GeoData : sarpy.io.complex.sicd_elements.GeoData.GeoDataType Position : sarpy.io.complex.sicd_elements.Position.PositionType Timeline : sarpy.io.complex.sicd_elements.Timeline.TimelineType Returns ------- None """ if self.PolarAngRefTime is None and SCPCOA.SCPTime is not None: self.PolarAngRefTime = SCPCOA.SCPTime if GeoData is None or GeoData.SCP is None or GeoData.SCP.ECF is None: return scp = GeoData.SCP.ECF.get_array() if SCPCOA.ARPPos is not None and SCPCOA.ARPVel is not None: scp = GeoData.SCP.ECF.get_array() etp = geocoords.wgs_84_norm(scp) arp = SCPCOA.ARPPos.get_array() los = (scp - arp) ulos = los / norm(los) look = SCPCOA.look arp_vel = SCPCOA.ARPVel.get_array() uspz = look * numpy.cross(arp_vel, ulos) uspz /= norm(uspz) if Grid is not None and Grid.ImagePlane is not None: if self.IPN is None: if Grid.ImagePlane == 'SLANT': self.IPN = XYZType.from_array(uspz) elif Grid.ImagePlane == 'GROUND': self.IPN = XYZType.from_array(etp) elif self.IPN is None: self.IPN = XYZType.from_array( uspz) # assuming slant -> most common if self.FPN is None: self.FPN = XYZType.from_array(etp) if Position is not None and \ Timeline is not None and Timeline.CollectDuration is not None and \ (self.PolarAngPoly is None or self.SpatialFreqSFPoly is None): pol_ref_pos = Position.ARPPoly(self.PolarAngRefTime) # fit the PFA polynomials times = numpy.linspace(0, Timeline.CollectDuration, 15) k_a, k_sf = self.pfa_polar_coords(Position, scp, times) self.PolarAngPoly = Poly1DType( Coefs=polynomial.polyfit(times, k_a, 5, full=False)) self.SpatialFreqSFPoly = Poly1DType( Coefs=polynomial.polyfit(k_a, k_sf, 5, full=False)) if Grid is not None and Grid.Row is not None and \ Grid.Row.KCtr is not None and Grid.Row.ImpRespBW is not None: if self.Krg1 is None: self.Krg1 = Grid.Row.KCtr - 0.5 * Grid.Row.ImpRespBW if self.Krg2 is None: self.Krg2 = Grid.Row.KCtr + 0.5 * Grid.Row.ImpRespBW if Grid is not None and Grid.Col is not None and \ Grid.Col.KCtr is not None and Grid.Col.ImpRespBW is not None: if self.Kaz1 is None: self.Kaz1 = Grid.Col.KCtr - 0.5 * Grid.Col.ImpRespBW if self.Kaz2 is None: self.Kaz2 = Grid.Col.KCtr + 0.5 * Grid.Col.ImpRespBW
def _check_image_plane(self, Grid, SCPCOA, SCP): """ Parameters ---------- Grid : sarpy.io.complex.sicd_elements.Grid.GridType SCPCOA : sarpy.io.complex.sicd_elements.SCPCOA.SCPCOAType SCP : numpy.ndarray Returns ------- bool """ if self.IPN is None or self.FPN is None: return True cond = True ipn = self.IPN.get_array(dtype='float64') fpn = self.FPN.get_array(dtype='float64') ETP = geocoords.wgs_84_norm(SCP) if Grid.ImagePlane == 'SLANT': try: ARP_Pos = SCPCOA.ARPPos.get_array(dtype='float64') ARP_Vel = SCPCOA.ARPVel.get_array(dtype='float64') uRG = SCP - ARP_Pos uRG /= numpy.linalg.norm(uRG) left = numpy.cross(ARP_Pos / numpy.linalg.norm(ARP_Pos), ARP_Vel / numpy.linalg.norm(ARP_Vel)) look = numpy.sign(numpy.dot(left, uRG)) Spn = -look * numpy.cross(uRG, ARP_Vel) uSpn = Spn / numpy.linalg.norm(Spn) if numpy.arccos(ipn.dot(uSpn)) > numpy.deg2rad(1): logging.error( 'The image formation algorithm is PFA, the Grid.ImagePlane is "SLANT", ' 'but COA slant plane and provided ipn are not within one degree of each other.' ) cond = False except (AttributeError, ValueError, TypeError): pass elif Grid.ImagePlane == 'GROUND': if numpy.arccos(ipn.dot(ETP)) > numpy.deg2rad(3): logging.error( 'The image formation algorithm is PFA, the Grid.ImagePlane is "Ground", ' 'but the Earth Tangent Plane at SCP and provided ipn are not within three ' 'degrees of each other.') cond = False # verify that fpn points outwards if fpn.dot(SCP) < 0: logging.error( 'The image formation algorithm is PFA, and the focus plane unit normal does not ' 'appear to be outward pointing') cond = False # check agreement between focus plane and ground plane if numpy.arccos(fpn.dot(ETP)) > numpy.deg2rad(3): logging.warning( 'The image formation algorithm is PFA, and the focus plane unit normal is not within ' 'three degrees of the earth Tangent Plane.') return cond