Source code for pygmt.src.solar

"""
solar - Plot day-night terminators and other sunlight parameters.
"""

from collections.abc import Sequence
from typing import Literal

import pandas as pd
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.exceptions import GMTValueError
from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias

__doctest_skip__ = ["solar"]


@fmt_docstring
@use_alias(B="frame", p="perspective")
@kwargs_to_strings(p="sequence")
def solar(
    self,
    terminator: Literal["astronomical", "civil", "day_night", "nautical"] = "day_night",
    terminator_datetime=None,
    fill: str | None = None,
    pen: str | None = None,
    projection: str | None = None,
    region: Sequence[float | str] | str | None = None,
    verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
    | bool = False,
    panel: int | tuple[int, int] | bool = False,
    transparency: float | None = None,
    **kwargs,
):
    r"""
    Plot day-night terminators and other sunlight parameters.

    This method plots the day-night terminator. Alternatively, it can plot the
    terminators for civil twilight, nautical twilight, or astronomical twilight.

    Full GMT docs at :gmt-docs:`solar.html`.

    {aliases}
       - G = fill
       - J = projection
       - R = region
       - T = terminator, **+d**/**+z**: terminator_datetime
       - V = verbose
       - W = pen
       - c = panel
       - t = transparency

    Parameters
    ----------
    terminator
        Set the type of terminator. Choose one of the following:

        - ``"astronomical"``: Astronomical twilight
        - ``"civil"``: Civil twilight
        - ``"day_night"``: Day-night terminator
        - ``"nautical"``: Nautical twilight

        Refer to https://en.wikipedia.org/wiki/Twilight for the definitions of different
        types of twilight.
    terminator_datetime : str or datetime object
        Set the date and time for the terminator calculation. It can be provided as a
        string or any datetime-like object recognized by :func:`pandas.to_datetime`. The
        time can be specified in UTC or using a UTC offset. The offset must be an
        integer number of hours (e.g., -8 or +5); fractional hours are truncated
        towards zero (e.g., -8.5 becomes -8 and +5.5 becomes +5). [Default is the
        current UTC date and time].
    {region}
    {projection}
    {frame}
    fill
        Set color or pattern for filling terminators [Default is no fill].
    pen
        Set pen attributes for lines [Default is ``"0.25p,black,solid"``].
    {verbose}
    {panel}
    {perspective}
    {transparency}

    Example
    -------
    >>> # import the Python module "datetime"
    >>> import datetime
    >>> import pygmt
    >>> # create a datetime object at 8:52:18 on June 24, 1997 (time in UTC)
    >>> date = datetime.datetime(
    ...     year=1997, month=6, day=24, hour=8, minute=52, second=18
    ... )
    >>> # create a new plot with pygmt.Figure()
    >>> fig = pygmt.Figure()
    >>> # create a map of the Earth with the coast method
    >>> fig.coast(land="darkgreen", water="lightblue", projection="W10c", region="d")
    >>> fig.solar(
    ...     # set the terminator to "day_night"
    ...     terminator="day_night",
    ...     # pass the datetime object
    ...     terminator_datetime=date,
    ...     # fill the night-section with navyblue at 75% transparency
    ...     fill="navyblue@75",
    ...     # draw the terminator with a 1-point black line
    ...     pen="1p,black",
    ... )
    >>> # show the plot
    >>> fig.show()
    """
    self._activate_figure()

    datetime_string, datetime_timezone = None, None
    if terminator_datetime:
        try:
            _datetime = pd.to_datetime(terminator_datetime)
            datetime_string = _datetime.strftime("%Y-%m-%dT%H:%M:%S.%f")
            # GMT's solar module uses the C 'atoi' function to parse the timezone
            # offset. Ensure the offset is an integer number of hours (e.g., -8 or +5).
            # Fractional hours (e.g., -8.5 or +5.5) are truncated towards zero.
            if utcoffset := _datetime.utcoffset():
                datetime_timezone = int(utcoffset.total_seconds() / 3600)
        except ValueError as verr:
            raise GMTValueError(terminator_datetime, description="datetime") from verr

    aliasdict = AliasSystem(
        G=Alias(fill, name="fill"),
        T=[
            Alias(
                terminator,
                name="terminator",
                mapping={
                    "day_night": "d",
                    "civil": "c",
                    "nautical": "n",
                    "astronomical": "a",
                },
            ),
            Alias(datetime_string, name="terminator_datetime", prefix="+d"),
            Alias(datetime_timezone, name="terminator_timezone", prefix="+z"),
        ],
        W=Alias(pen, name="pen"),
    ).add_common(
        J=projection,
        R=region,
        V=verbose,
        c=panel,
        t=transparency,
    )
    aliasdict.merge(kwargs)

    with Session() as lib:
        lib.call_module(module="solar", args=build_arg_list(aliasdict))