def locdir_at(self, phi, theta, psi, row, col, alt=0, geo=True):
        """
        Given the satellite attitude, computes lon, lat of point row, col, alt.

        Args:
            phi, theta, psi: attitude angles of the satellite camera
            row, col: pixel coordinates in the image plane
            alt: altitude of the 3d point above the Earth surface
            geo: boolean flag telling whether to return geographic
                coordinates or Cartesian coordinates.

        Returns:
            geographic coordinates (lon, lat), or Cartesian coordinates
            (x, y, z). Cartesian coordinates are computed with respect to the
            orbital frame.
        """
        # first compute coordinates of the 3-space point in the orbital frame
        orbital_to_camera = utils.rotation_3d('xyz', phi, theta, psi)
        v = np.dot(orbital_to_camera, self.instrument.sight_vector(col))
        c = np.array([0, 0, self.orbit.radius])
        r = PhysicalConstants.earth_radius + alt
        p = utils.intersect_line_and_sphere(v, c, r)
        if not geo:
            return p

        # then convert them to lon, lat, using t to get the satellite position
        if p is None:
            return p
        else:
            t = row * self.instrument.dwell_time
            mat_t_ol = self.rotational_to_orbital(t)
            return utils.lon_lat(np.dot(mat_t_ol, -c + p))
    def target_point_orbital_frame(self, a, b, alt=0):
        """
        Computes orbital coordinates of the ground point targeted by the
        satellite.

        Args:
            a: sight direction angle about the x axis of orbital frame, degrees
            b: angle about the y axis of orbital frame, degrees
            alt: altitude of the ground above the sphere

        Returns:
            numpy 1x3 array with the coordinates in the orbital frame
        """
        # Earth center position in the orbital frame
        c = np.array([0, 0, self.radius])

        # coordinates of the vector defined by angles a, b and coordinate z=1
        # in the orbital frame
        a_rad = a * np.pi/180
        b_rad = b * np.pi/180
        v = np.array([np.tan(b_rad), -np.tan(a_rad), 1])

        # intersection of the line directed by v with the Earth
        r = PhysicalConstants.earth_radius + alt
        return utils.intersect_line_and_sphere(v, c, r)
    def locdir_at(self, phi, theta, psi, row, col, alt=0, geo=True):
        """
        Given the satellite attitude, computes lon, lat of point row, col, alt.

        Args:
            phi, theta, psi: attitude angles of the satellite camera
            row, col: pixel coordinates in the image plane
            alt: altitude of the 3d point above the Earth surface
            geo: boolean flag telling whether to return geographic
                coordinates or Cartesian coordinates.

        Returns:
            geographic coordinates (lon, lat), or Cartesian coordinates
            (x, y, z). Cartesian coordinates are computed with respect to the
            orbital frame.
        """
        # first compute coordinates of the 3-space point in the orbital frame
        orbital_to_camera = utils.rotation_3d('xyz', phi, theta, psi)
        v = np.dot(orbital_to_camera, self.instrument.sight_vector(col))
        c = np.array([0, 0, self.orbit.radius])
        r = PhysicalConstants.earth_radius + alt
        p = utils.intersect_line_and_sphere(v, c, r)
        if not geo:
            return p

        # then convert them to lon, lat, using t to get the satellite position
        if p is None:
            return p
        else:
            t = row * self.instrument.dwell_time
            mat_t_ol = self.rotational_to_orbital(t)
            return utils.lon_lat(np.dot(mat_t_ol, -c + p))
    def target_point_orbital_frame(self, a, b, alt=0):
        """
        Computes orbital coordinates of the ground point targeted by the
        satellite.

        Args:
            a: sight direction angle about the x axis of orbital frame, degrees
            b: angle about the y axis of orbital frame, degrees
            alt: altitude of the ground above the sphere

        Returns:
            numpy 1x3 array with the coordinates in the orbital frame
        """
        # Earth center position in the orbital frame
        c = np.array([0, 0, self.radius])

        # coordinates of the vector defined by angles a, b and coordinate z=1
        # in the orbital frame
        a_rad = a * np.pi / 180
        b_rad = b * np.pi / 180
        v = np.array([np.tan(b_rad), -np.tan(a_rad), 1])

        # intersection of the line directed by v with the Earth
        r = PhysicalConstants.earth_radius + alt
        return utils.intersect_line_and_sphere(v, c, r)
    def pixel_projection_width(self, m, x=0, y=0):
        """
        Computes the width of the projection of a pixel on the ground.

        Args:
            m: numpy 3x3 array representing the change of
               coordinates matrix between orbital frame and camera frame
            x, y (optional, default is 0, 0): coordinates of the pixel in the
                image plane

        Returns:
            width, in meters, of the projection of the pixel on the ground, in
            the pushbroom direction (orthogonal to the pushbroom movement)
        """
        # pixel width and focal length in mm
        w = self.instrument.w_pix * 0.001
        f = self.instrument.focal * 1000

        # direction of the camera, expressed in the orbital frame
        v = np.dot(m, np.array([-x, -y, f]))

        # intersection of the line directed by v with the Earth
        c = np.array([0, 0, self.orbit.radius])
        r = PhysicalConstants.earth_radius
        p = utils.intersect_line_and_sphere(v, c, r)

        # normal to the tangent plane to the Earth at the intersection point p
        n = p - c

        # projection of the left and right borders of the pixel on the ground
        v1 = np.dot(m, np.array([-(x + .5*w), -y, f]))
        v2 = np.dot(m, np.array([-(x - .5*w), -y, f]))
        p1 = utils.intersect_line_and_plane(v1, p, n)
        p2 = utils.intersect_line_and_plane(v2, p, n)

        return np.linalg.norm(p1 - p2)
    def pixel_projection_width(self, m, x=0, y=0):
        """
        Computes the width of the projection of a pixel on the ground.

        Args:
            m: numpy 3x3 array representing the change of
               coordinates matrix between orbital frame and camera frame
            x, y (optional, default is 0, 0): coordinates of the pixel in the
                image plane

        Returns:
            width, in meters, of the projection of the pixel on the ground, in
            the pushbroom direction (orthogonal to the pushbroom movement)
        """
        # pixel width and focal length in mm
        w = self.instrument.w_pix * 0.001
        f = self.instrument.focal * 1000

        # direction of the camera, expressed in the orbital frame
        v = np.dot(m, np.array([-x, -y, f]))

        # intersection of the line directed by v with the Earth
        c = np.array([0, 0, self.orbit.radius])
        r = PhysicalConstants.earth_radius
        p = utils.intersect_line_and_sphere(v, c, r)

        # normal to the tangent plane to the Earth at the intersection point p
        n = p - c

        # projection of the left and right borders of the pixel on the ground
        v1 = np.dot(m, np.array([-(x + .5 * w), -y, f]))
        v2 = np.dot(m, np.array([-(x - .5 * w), -y, f]))
        p1 = utils.intersect_line_and_plane(v1, p, n)
        p2 = utils.intersect_line_and_plane(v2, p, n)

        return np.linalg.norm(p1 - p2)