libera_utils.footprint_matching.geometry.project_to_angular#

libera_utils.footprint_matching.geometry.project_to_angular(cell_lats_deg: ndarray, cell_lons_deg: ndarray, *, frame: ViewingFrame | None = None, boresight_lat_deg: float | None = None, boresight_lon_deg: float | None = None, subsatellite_lat_deg: float | None = None, subsatellite_lon_deg: float | None = None, satellite_radius_km: float | None = None, cell_ecef_km: ndarray | None = None) → tuple[ndarray, ndarray]#

Project ground grid cells into the radiometer’s angular frame (delta, beta).

This is the inverse of the forward rotation _offset_ray_direction used to build the footprint bounding box, and the piece the PSF aggregation weighting needs: given where each ancillary grid cell sits on the ground, how far off the boresight is it in the along-scan (delta) and cross-scan (beta) angular directions as seen from the satellite. Those angles are exactly the coordinates the CERES PSF libera_utils.footprint_matching.psf.psf_weight() is a function of (CERES ATBD v2.2 §4.4.2.3, Eq. 4.4-8).

Pass either a precomputed single-footprint frame or the footprint’s geolocation (all of boresight_lat_deg, boresight_lon_deg, subsatellite_lat_deg, subsatellite_lon_deg and satellite_radius_km), from which the frame is built.

Method#

The viewing frame (compute_viewing_frames()) gives three orthonormal axes:

  • b_hat – the boresight look direction (delta = beta = 0),

  • c_hat – the cross-scan axis (perpendicular to the scan plane),

  • n_hat = c_hat x b_hat – the in-scan-plane axis (b_hat’s +delta side).

For each cell we form the unit look vector from the satellite to the cell and read off the angles by projecting onto that basis. Because _offset_ray_direction produces a direction d = (b_hat cos(delta) + n_hat sin(delta)) cos(beta) - c_hat sin(beta) (the beta rotation is about c_hat x along), the inverse is exact:

beta  = asin(-look . c_hat)
delta = atan2(look . n_hat, look . b_hat)

The elongation of the footprint at oblique viewing is captured by projecting the actual ground cells to satellite angles, so no viewing-zenith stretch factor is needed.

param cell_lats_deg:

Grid-cell latitudes and longitudes in degrees (any common shape). Assumed on the surface (ellipsoidal height 0).

type cell_lats_deg:

np.ndarray

param cell_lons_deg:

Grid-cell latitudes and longitudes in degrees (any common shape). Assumed on the surface (ellipsoidal height 0).

type cell_lons_deg:

np.ndarray

param frame:

A precomputed single-footprint frame, as returned by ViewingFrame.for_index(). When given, the geolocation arguments are ignored.

type frame:

ViewingFrame, optional

param boresight_lat_deg:

Footprint boresight centroid, degrees (delta = beta = 0 anchor).

type boresight_lat_deg:

float, optional

param boresight_lon_deg:

Footprint boresight centroid, degrees (delta = beta = 0 anchor).

type boresight_lon_deg:

float, optional

param subsatellite_lat_deg:

Geodetic spacecraft sub-point (L1B Subsatellite_Latitude/Longitude), degrees.

type subsatellite_lat_deg:

float, optional

param subsatellite_lon_deg:

Geodetic spacecraft sub-point (L1B Subsatellite_Latitude/Longitude), degrees.

type subsatellite_lon_deg:

float, optional

param satellite_radius_km:

Spacecraft distance from Earth’s center (L1B Radius_of_Satellite_from_Center_of_Earth), km.

type satellite_radius_km:

float, optional

param cell_ecef_km:

Precomputed surface ECEF (km) of the cells, (N, 3) in cell_lats_deg.ravel() order (i.e. exactly surface_cell_ecef_km()). The cells’ geometry is independent of the viewing frame, so when several footprints see the same cells the caller can compute this once and pass it in, skipping the per-footprint closed-form geodetic->ECEF transform. When omitted it is computed from the cell coordinates; the result is identical either way.

type cell_ecef_km:

np.ndarray, optional

returns:

(delta_deg, beta_deg) arrays, each the shape of cell_lats_deg.

rtype:

tuple[np.ndarray, np.ndarray]

raises ValueError:

If neither frame nor the full geolocation is given, or frame does not hold exactly one footprint.

raises MissingGeolocationError:

If the ephemeris fields are unusable or the footprint is not visible from the satellite.

raises OffLimbError:

If the boresight latitude or longitude is fill, non-finite or out of range.