diff --git a/doc/api/index.rst b/doc/api/index.rst index f0275cc1e2f..0f673b90d5b 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -38,6 +38,7 @@ Plotting data and laying out the map: Figure.plot3d Figure.set_panel Figure.shift_origin + Figure.solar Figure.subplot Figure.text diff --git a/examples/gallery/embellishments/solar.py b/examples/gallery/embellishments/solar.py new file mode 100644 index 00000000000..e8cc01a9850 --- /dev/null +++ b/examples/gallery/embellishments/solar.py @@ -0,0 +1,48 @@ +""" +Day-night terminator line and twilights +--------------------------------------- + +Use :meth:`pygmt.Figure.solar` to show the different transition stages between daytime +and nightime. The parameter ``terminator`` is used to set the twilight stage, and can be +either 'day-night' (brightest), 'civil', 'nautical', or 'astronomical' (darkest). Refer +to https://en.wikipedia.org/wiki/Twilight for more information. +""" +import datetime + +import pygmt + +fig = pygmt.Figure() +# Create a figure showing the global region on a Mollweide projection +# Land color is set to dark green and water color is set to light blue +fig.coast(region="d", projection="W0/15c", land="darkgreen", water="lightblue") +# Set a time for the day-night terminator and twilights, 1700 UTC on January 1, 2000 +terminator_datetime = datetime.datetime( + year=2000, month=1, day=1, hour=17, minute=0, second=0 +) +# Set the pen line to be 0.5p thick +# Set the fill for the night area to be navy blue at different transparency levels +fig.solar( + terminator="day_night", + terminator_datetime=terminator_datetime, + fill="navyblue@95", + pen="0.5p", +) +fig.solar( + terminator="civil", + terminator_datetime=terminator_datetime, + fill="navyblue@85", + pen="0.5p", +) +fig.solar( + terminator="nautical", + terminator_datetime=terminator_datetime, + fill="navyblue@80", + pen="0.5p", +) +fig.solar( + terminator="astronomical", + terminator_datetime=terminator_datetime, + fill="navyblue@80", + pen="0.5p", +) +fig.show() diff --git a/pygmt/figure.py b/pygmt/figure.py index acd448b9472..a8d89b1e727 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -391,6 +391,7 @@ def _repr_html_(self): plot, plot3d, set_panel, + solar, subplot, text, ) diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 1ea68b899fc..7fae8fd8a2d 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -25,6 +25,7 @@ from pygmt.src.meca import meca from pygmt.src.plot import plot from pygmt.src.plot3d import plot3d +from pygmt.src.solar import solar from pygmt.src.subplot import set_panel, subplot from pygmt.src.surface import surface from pygmt.src.text import text_ as text # "text" is an argument within "text_" diff --git a/pygmt/src/solar.py b/pygmt/src/solar.py new file mode 100644 index 00000000000..20bcfe6dfd7 --- /dev/null +++ b/pygmt/src/solar.py @@ -0,0 +1,97 @@ +""" +solar - Plot day-night terminators and twilight. +""" +import pandas as pd +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias + + +@fmt_docstring +@use_alias( + B="frame", + G="fill", + J="projection", + R="region", + U="timestamp", + V="verbose", + W="pen", + X="xshift", + Y="yshift", + c="panel", + p="perspective", + t="transparency", +) +@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence") +def solar(self, terminator="d", terminator_datetime=None, **kwargs): + r""" + Plot day-light terminators or twilights. + + This function plots the day-night terminator. Alternatively, it can plot + the terminators for civil twilight, nautical twilight, or astronomical + twilight. + + Full parameter list at :gmt-docs:`solar.html` + + {aliases} + + Parameters + ---------- + terminator : str + Set the type of terminator displayed. Valid arguments are + **day_night**, **civil**, **nautical**, and **astronomical**, which + can be set with either the full name or the first letter of the name. + [Default is **day_night**] + + Refer to https://en.wikipedia.org/wiki/Twilight for the definitions of + different types of twilight. + terminator_datetime : str or datetime object + Set the UTC date and time of the displayed terminator. It can be + passed as a string or Python datetime object. + [Default is the current UTC date and time] + {R} + {J} + {B} + fill : str + Color or pattern for filling of terminators. + pen : str + Set pen attributes for lines. The default pen + is ``default,black,solid``. + {U} + {V} + {XY} + {c} + {p} + {t} + """ + + kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access + if "T" in kwargs: + raise GMTInvalidInput( + "Use 'terminator' and 'terminator_datetime' instead of 'T'." + ) + if terminator not in [ + "day_night", + "civil", + "nautical", + "astronomical", + "d", + "c", + "n", + "a", + ]: + raise GMTInvalidInput( + f"Unrecognized solar terminator type '{terminator}'. Valid values " + "are 'day_night', 'civil', 'nautical', and 'astronomical'." + ) + kwargs["T"] = terminator[0] + if terminator_datetime: + try: + datetime_string = pd.to_datetime(terminator_datetime).strftime( + "%Y-%m-%dT%H:%M:%S.%f" + ) + except ValueError as verr: + raise GMTInvalidInput("Unrecognized datetime format.") from verr + kwargs["T"] += f"+d{datetime_string}" + with Session() as lib: + lib.call_module("solar", build_arg_string(kwargs)) diff --git a/pygmt/tests/baseline/test_solar_set_terminator_datetime.png.dvc b/pygmt/tests/baseline/test_solar_set_terminator_datetime.png.dvc new file mode 100644 index 00000000000..caaab7eedce --- /dev/null +++ b/pygmt/tests/baseline/test_solar_set_terminator_datetime.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 0a7f4959b500b6fa3a560a6368db0f90 + size: 25982 + path: test_solar_set_terminator_datetime.png diff --git a/pygmt/tests/baseline/test_solar_terminators.png.dvc b/pygmt/tests/baseline/test_solar_terminators.png.dvc new file mode 100644 index 00000000000..c7f3b4f2516 --- /dev/null +++ b/pygmt/tests/baseline/test_solar_terminators.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 54c92bea64e0fc76c62601cb6131f22e + size: 44160 + path: test_solar_terminators.png diff --git a/pygmt/tests/test_solar.py b/pygmt/tests/test_solar.py new file mode 100644 index 00000000000..d487dc30fc3 --- /dev/null +++ b/pygmt/tests/test_solar.py @@ -0,0 +1,120 @@ +""" +Tests for solar. +""" +import datetime + +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput + + +@pytest.mark.mpl_image_compare +def test_solar_terminators(): + """ + Test passing the solar argument with a time string and no terminator type + to confirm the default terminator type. + """ + fig = Figure() + fig.basemap(region="d", projection="W0/15c", frame="a") + fig.solar( + terminator="d", + pen="1p,blue", + terminator_datetime="1990-02-17 04:25:00", + ) + fig.solar( + terminator="a", + pen="1p,red", + terminator_datetime="1990-02-17 04:25:00", + ) + fig.solar( + terminator="c", + pen="1p,green", + terminator_datetime="1990-02-17 04:25:00", + ) + fig.solar( + terminator="n", + pen="1p,yellow", + terminator_datetime="1990-02-17 04:25:00", + ) + return fig + + +@pytest.mark.mpl_image_compare(filename="test_solar_set_terminator_datetime.png") +@pytest.mark.parametrize( + "terminator_datetime", + [ + pytest.param("1990-02-17 04:25:00", id="terminator_datetime_string"), + datetime.datetime(year=1990, month=2, day=17, hour=4, minute=25, second=0), + ], +) +def test_solar_set_terminator_datetime(terminator_datetime): + """ + Test passing the solar argument with the day_night terminator and a + datetime string. + """ + fig = Figure() + fig.solar( + region="d", + projection="W0/15c", + frame="a", + terminator="day_night", + terminator_datetime=terminator_datetime, + ) + return fig + + +def test_invalid_terminator_type(): + """ + Test if solar fails when it receives an invalid terminator type. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.solar( + region="d", + projection="W0/15c", + frame="a", + terminator="invalid", + ) + + +def test_invalid_parameter(): + """ + Test if solar fails when it receives a GMT argument for 'T' instead of the + PyGMT arguments for 'terminator' and 'terminator_datetime'. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + # Use single-letter option 'T' for testing + fig.solar( + region="d", projection="W0/15c", frame="a", T="d+d1990-02-17T04:25:00" + ) + + +def test_invalid_datetime(): + """ + Test if solar fails when it receives an invalid datetime string. + """ + fig = Figure() + with pytest.raises(GMTInvalidInput): + fig.solar( + region="d", + projection="W0/15c", + frame="a", + terminator_datetime="199A-02-17 04:25:00", + ) + + +@pytest.mark.mpl_image_compare(filename="test_solar_set_terminator_datetime.png") +def test_solar_default_terminator(): + """ + Test passing the solar argument with a time string and no terminator type + to confirm the default terminator type. + """ + fig = Figure() + fig.solar( + region="d", + projection="W0/15c", + frame="a", + terminator_datetime="1990-02-17 04:25:00", + ) + return fig