The Viewing Geometry#

The viewing geometry can be thought of as specifying what output quantities we need from the model. These can be things like radiances at the top of the atmosphere, ground, or anywhere inbetween. Or they can be fluxes that are automatically integrated by the model.

Radiance Output#

We specify radiance output through a list of single positions and look vectors. These are stored inside the sasktran2.ViewingGeometry object.

import sasktran2 as sk

viewing_geo = sk.ViewingGeometry()

Rather than specifying look directions and positions directly, they are specified through objects that we colloquially refer to as Viewing Policies. One example of a viewing policy is the sasktran2.TangentAltitudeSolar object, which defines a position and viewing direction based off an observer altitude, as well as various parameters that are defined at the tangent point of the measurement. For example,

ray = sk.TangentAltitudeSolar(tangent_altitude_m=10000,
                                relative_azimuth=0,
                                observer_altitude_m=200000,
                                cos_sza=0.6)

To add it to our overall container, we can do,

viewing_geo.add_ray(ray)

And we can look at all of the rays in our container

viewing_geo.observer_rays
[Tangent Viewing Ray: tangentaltitude: 10000, relative_azimuth_angle: 0, observeraltitude: 200000, cos_sza: 0.6]

Geometry-relative tangent rays#

For a sasktran2.Geometry2D atmosphere, the recommended limb viewing policy is sasktran2.TangentAltitude. Unlike sasktran2.TangentAltitudeSolar, it places the ray directly in the coordinate system of the atmospheric grid and does not use solar angles to choose the tangent point.

The Geometry2D horizontal plane is spanned by the coordinate reference-z and reference-x directions. Its convention is:

  • horizontal angle 0 is the Geometry2D reference point;

  • positive horizontal angles rotate from reference-z toward reference-x;

  • the reference-y direction is normal to this plane and is the direction in which the 2D atmosphere is invariant.

The ray needs two angles. horizontal_angle_radians selects the tangent point’s position on the Geometry2D horizontal grid. viewing_azimuth_radians selects the line-of-sight direction within the local tangent plane:

Viewing azimuth

Line-of-sight direction at the tangent point

0

In-plane, toward increasing horizontal angle

pi

In-plane, toward decreasing horizontal angle

+pi/2

Positive invariant/reference-y direction

-pi/2

Negative invariant/reference-y direction

The direction is the propagation direction from the observer, through the tangent point, and toward the far side of the atmosphere. Thus azimuth 0 places the observer on the decreasing-horizontal-angle side of the tangent point, while azimuth pi places it on the increasing-angle side. A straight ray with azimuth +/-pi/2 remains at one Geometry2D horizontal coordinate.

import numpy as np

ray = sk.TangentAltitude(
    tangent_altitude_m=20_000.0,
    observer_altitude_m=200_000.0,
    horizontal_angle_radians=0.1,
    viewing_azimuth_radians=0.0,
)
viewing_geo.add_ray(ray)

The Sun is still specified by cos_sza and solar_azimuth on the model geometry. In particular, solar_azimuth=0 puts the Sun in the Geometry2D plane toward increasing horizontal angle at the reference point, while solar_azimuth=pi/2 puts the Sun normal to that plane. Changing those solar parameters changes the illumination and scattering angles, but it does not move a TangentAltitude ray relative to the atmospheric grid.

The ray policy does not take a Geometry2D object in its constructor. The sasktran2.Engine supplies the model coordinate system when it constructs the internal observer position and look direction. This also lets the same policy operate with a spherical Geometry1D atmosphere, where its horizontal placement is immaterial.

Viewing Policies#

sasktran2.TangentAltitude(...)

A geometry-relative limb viewing ray defined at its tangent point.

sasktran2.TangentAltitudeSolar(...)

Defines a viewing ray from the observer altitude, and tangent point parameters.

sasktran2.GroundViewingSolar(cos_sza, ...)

Defines a viewing ray that is looking at the ground from angles defined at the ground location. Note that

sasktran2.SolarAnglesObserverLocation(...)

Defines a viewing ray that is defined at a location defined from the solar angles.

Working with Real Measurements#

The Solar Geometry Handler#

sasktran2 contains utilities to compute the solar angles automatically for a given location and time using the astropy package. To use this functionality you must have astropy installed.

For example, to calculate the (solar zenith angle, solar azimuth angle) at a given location we can do,

import pandas as pd


solar = sk.solar.SolarGeometryHandlerAstropy()

print(solar.target_solar_angles(
    latitude=20,
    longitude=-100,
    altitude=0,
    time=pd.Timestamp("2024-11-12 20:00:00")
))
(np.float64(44.66518717640884), np.float64(213.30155059850907))

Solar azimuth angles are always measured from true north, with 90 degrees pointing in the east direction.

Note

The solar handler specifies angles in degrees, whereas most other aspects of SASKTRAN2 use radians.

Converting from ECEF Coordinates#

In the original sasktran1 package, all rays were specified as a triplet of (observer position, local look vector, time) in Earth Centered Earth Fixed coordinates.

Here we have pre-computed the position and look vector of a satellite directly above Saskatoon, Canada, and we will specify a local time of noon (18 UTC).

import numpy as np

observer = np.array([-1158730.59368676, -3875262.18406142,  5170772.5134034 ])
look_vector = np.array([ 0.17579194,  0.58791911, -0.78958743])

time = pd.Timestamp("2024-11-12 18:00:00")

We can then convert the ray to one sasktran2 recognizes,

ray = sk.viewinggeo.ecef_to_sasktran2_ray(
    observer=observer,
    look_vector=look_vector,
    time=time,
    solar_handler=solar
)
print(ray)
Ground Viewing Ray: cos_sza: 0.3258368629858771, relative_azimuth_angle: 2.9184752483795724, cos_viewing_zenith: 1.0, observer_altitude_m: 200000.00000000442

Note here we re-used our solar handler from the previous section.

Flux Output#

Fluxes can be internally calcluated in sasktran2 by specifying a location to calculate the fluxes at with the sasktran2.ViewingGeometry.add_flux_observer() method.

Note

Currently flux output is only supported when using the DiscreteOrdinates source, in PlaneParallel mode, i.e.

config = sk.Config()
config.single_scatter_source = sk.SingleScatterSource.DiscreteOrdinates
config.multiple_scatter_source = sk.MultipleScatterSource.DiscreteOrdinates

model_geometry = sk.Geometry1D(...,
                               geometry_type=sk.GeometryType.PlaneParallel)


Flux Observer Policies#

sasktran2.FluxObserverSolar(cos_sza, ...)

Defines a flux observer that is defined at a location defined from the solar angles.