Source code for hawcsimulator.steps.limb_observation
from __future__ import annotations
import numpy as np
import pandas as pd
import sasktran2 as sk
from hamilton.function_modifiers import config
from hawcsimulator.datastructures.viewinggeo import ObservationContainer
from hawcsimulator.geometry.observation import SimulatedObservationGeometry
[docs]
@config.when(observation_method="limb")
def observation__limb(
viewing_tangent_altitudes: np.ndarray,
time: pd.Timestamp,
tangent_latitude: float,
tangent_longitude: float,
observer_altitude: float,
sample_wavelengths: np.ndarray,
observer_longitude: float | None = None,
observer_latitude: float | None = None,
tangent_solar_zenith_angle: float | None = None,
tangent_solar_azimuth_angle: float | None = None,
maximum_allowed_sza: float = 88,
) -> ObservationContainer:
"""
Creates an idealized limb viewing observation based on a set of viewing tangent altitudes and
optionally solar angles at the tangent point. The observation is created assuming the solar angles
are the same for every tangent altitude.
Parameters
----------
viewing_tangent_altitudes : np.array
Tangent altitudes for the observation in [m], assuming no refraction
time : pd.Timestamp
Time of the observation. Primarily used when the solar angles are not specified to calculate
the sun position
tangent_latitude : float
Tangent latitude in [degrees]
tangent_longitude : float
Tangent longitude in [degrees]
observer_altitude : float
Altitude of the observer in [m]
sample_wavelengths : np.ndarray
Observation sample wavelengths for the instrument in [nm]
observer_latitude: float
Latitude of the observer in [degrees], optional, only required if time based solar angles are used
observer_longitude: float
Longitude of the observer in [degrees], optional, only required if time based solar angles are used
tangent_solar_zenith_angle : float | None, optional
Solar zenith angle in [degrees], by default None indicating it will be calculated from the observation time
tangent_solar_azimuth_angle : float | None, optional
Relative solar azimuth angle in [degrees] where 0 degrees is forward scatter, by default None indicating it will be
calculated from the observation time
Returns
-------
ObservationContainer
"""
tan_alts = viewing_tangent_altitudes
obs_time = time
if (
tangent_solar_zenith_angle is not None
and tangent_solar_azimuth_angle is not None
):
# Forced angles
solar_handler = sk.solar.SolarGeometryHandlerForced(
tangent_solar_zenith_angle, tangent_solar_azimuth_angle
)
viewing_azimuth = 0.0
else:
# Time angles
solar_handler = sk.solar.SolarGeometryHandlerAstropy()
if observer_latitude is None or observer_longitude is None:
msg = "Have to specify observer_latitude and observer_longitude when using time based solar angles"
raise ValueError(msg)
# Here we also have to calculate the viewing azimuth angle
tangent_geo = sk.geodetic.WGS84()
tangent_geo.from_lat_lon_alt(tangent_latitude, tangent_longitude, 25000.0)
obs_geo = sk.geodetic.WGS84()
obs_geo.from_lat_lon_alt(
observer_latitude, observer_longitude, observer_altitude
)
viewing_ray = tangent_geo.location - obs_geo.location
viewing_ray = viewing_ray / np.linalg.norm(viewing_ray)
north = -1 * tangent_geo.local_south # x axis
east = -1 * tangent_geo.local_west # y axis
viewing_azimuth = np.arctan2(
np.dot(east, viewing_ray), np.dot(north, viewing_ray)
)
viewing_geo = sk.viewinggeo.LimbVertical.from_tangent_parameters(
solar_handler=solar_handler,
tangent_altitudes=tan_alts,
tangent_latitude=tangent_latitude,
tangent_longitude=tangent_longitude,
time=obs_time,
observer_altitude=observer_altitude,
viewing_azimuth=viewing_azimuth,
)
obs_sza = np.rad2deg(np.arccos(viewing_geo.recommended_cos_sza()))
if obs_sza > maximum_allowed_sza:
msg = f"Observation SZA: {obs_sza} is greater than the allowed maximum: {maximum_allowed_sza}"
raise ValueError(msg)
return ObservationContainer(
SimulatedObservationGeometry(
viewing_geo=viewing_geo,
sample_wavel=sample_wavelengths,
),
obs_time,
)