diff --git a/doc/api/index.rst b/doc/api/index.rst index 58a41dd8cec..157e2921873 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..fdfb335630b --- /dev/null +++ b/examples/gallery/embellishments/solar.py @@ -0,0 +1,46 @@ +""" +Day-night terminator line and twilights +--------------------------------------- + +Use :meth:`pygmt.Figure.solar` to plot the day-night terminator line, +and civil, nautical, astronomical twilights. +""" +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 1p 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 93930a3a55c..60dd1ed0e15 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..aa621abc6f6 --- /dev/null +++ b/pygmt/src/solar.py @@ -0,0 +1,99 @@ +""" +solar - Plot day-night terminators. +""" + +import datetime + +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. + + This function plots the day-night terminator. Additionally, it can + plot the terminators for civil twilight, nautical twilight, and + 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**] + terminator_datetime : str or datetime object + Set the UTC date and time of the displayed terminator. It can be + passed as a string or a 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} + """ + # TODO: Incorporate "-T+zTZ" + + 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", + "nautical", + "civil", + "astronomical", + "astro", + "d", + "n", + "c", + "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_coast.png b/pygmt/tests/baseline/test_coast.png deleted file mode 100644 index 9a31355f638..00000000000 Binary files a/pygmt/tests/baseline/test_coast.png and /dev/null differ diff --git a/pygmt/tests/baseline/test_coast.png.dvc b/pygmt/tests/baseline/test_coast.png.dvc new file mode 100644 index 00000000000..dc66fee1240 --- /dev/null +++ b/pygmt/tests/baseline/test_coast.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: a054a1df3a6eb6f6799ba0fc104ade45 + size: 135451 + path: test_coast.png diff --git a/pygmt/tests/baseline/test_coast_aliases.png b/pygmt/tests/baseline/test_coast_aliases.png deleted file mode 100644 index 25d33a151cd..00000000000 Binary files a/pygmt/tests/baseline/test_coast_aliases.png and /dev/null differ diff --git a/pygmt/tests/baseline/test_coast_aliases.png.dvc b/pygmt/tests/baseline/test_coast_aliases.png.dvc new file mode 100644 index 00000000000..6f168b0088c --- /dev/null +++ b/pygmt/tests/baseline/test_coast_aliases.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: d53aa16264ebd5ae546c95b88855b16a + size: 519870 + path: test_coast_aliases.png diff --git a/pygmt/tests/baseline/test_coast_dcw_continent.png.dvc b/pygmt/tests/baseline/test_coast_dcw_continent.png.dvc new file mode 100644 index 00000000000..e19bc1edcf8 --- /dev/null +++ b/pygmt/tests/baseline/test_coast_dcw_continent.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 479f5ea4fb662289c4bf0e9fc1d4ae4b + size: 91352 + path: test_coast_dcw_continent.png diff --git a/pygmt/tests/baseline/test_coast_dcw_list.png.dvc b/pygmt/tests/baseline/test_coast_dcw_list.png.dvc new file mode 100644 index 00000000000..28c7e8d07b3 --- /dev/null +++ b/pygmt/tests/baseline/test_coast_dcw_list.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 30c3f4c90ba740b7d9edae97d9d3b853 + size: 93411 + path: test_coast_dcw_list.png diff --git a/pygmt/tests/baseline/test_coast_dcw_multiple.png.dvc b/pygmt/tests/baseline/test_coast_dcw_multiple.png.dvc new file mode 100644 index 00000000000..5bd0798c1da --- /dev/null +++ b/pygmt/tests/baseline/test_coast_dcw_multiple.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: c68c4ecaffdda779a6b20aba786dd919 + size: 94025 + path: test_coast_dcw_multiple.png diff --git a/pygmt/tests/baseline/test_coast_dcw_single.png.dvc b/pygmt/tests/baseline/test_coast_dcw_single.png.dvc new file mode 100644 index 00000000000..38a0b2b1a5d --- /dev/null +++ b/pygmt/tests/baseline/test_coast_dcw_single.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 40fcf70e1f73ba2be60cbf9e3f15794d + size: 83794 + path: test_coast_dcw_single.png diff --git a/pygmt/tests/baseline/test_coast_dcw_state.png.dvc b/pygmt/tests/baseline/test_coast_dcw_state.png.dvc new file mode 100644 index 00000000000..4710974e8f7 --- /dev/null +++ b/pygmt/tests/baseline/test_coast_dcw_state.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: faddbe172a81a10563eebf75744d7339 + size: 110297 + path: test_coast_dcw_state.png diff --git a/pygmt/tests/baseline/test_coast_iceland.png b/pygmt/tests/baseline/test_coast_iceland.png deleted file mode 100644 index 14a33962956..00000000000 Binary files a/pygmt/tests/baseline/test_coast_iceland.png and /dev/null differ diff --git a/pygmt/tests/baseline/test_coast_iceland.png.dvc b/pygmt/tests/baseline/test_coast_iceland.png.dvc new file mode 100644 index 00000000000..d71d76861b0 --- /dev/null +++ b/pygmt/tests/baseline/test_coast_iceland.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: c4efb846d4f0a26c7c3b4900ae0043bf + size: 23420 + path: test_coast_iceland.png diff --git a/pygmt/tests/baseline/test_coast_world_mercator.png b/pygmt/tests/baseline/test_coast_world_mercator.png deleted file mode 100644 index 27f9b16fd57..00000000000 Binary files a/pygmt/tests/baseline/test_coast_world_mercator.png and /dev/null differ diff --git a/pygmt/tests/baseline/test_coast_world_mercator.png.dvc b/pygmt/tests/baseline/test_coast_world_mercator.png.dvc new file mode 100644 index 00000000000..d77e879dca1 --- /dev/null +++ b/pygmt/tests/baseline/test_coast_world_mercator.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 12884b768aa463d575019347dedfa6b0 + size: 322546 + path: test_coast_world_mercator.png diff --git a/pygmt/tests/baseline/test_solar_default_terminator.png.dvc b/pygmt/tests/baseline/test_solar_default_terminator.png.dvc new file mode 100644 index 00000000000..6bc9e740fff --- /dev/null +++ b/pygmt/tests/baseline/test_solar_default_terminator.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 0a7f4959b500b6fa3a560a6368db0f90 + size: 25982 + path: test_solar_default_terminator.png diff --git a/pygmt/tests/baseline/test_solar_set_terminator_datetime_object.png.dvc b/pygmt/tests/baseline/test_solar_set_terminator_datetime_object.png.dvc new file mode 100644 index 00000000000..61b633b93ca --- /dev/null +++ b/pygmt/tests/baseline/test_solar_set_terminator_datetime_object.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 0a7f4959b500b6fa3a560a6368db0f90 + size: 25982 + path: test_solar_set_terminator_datetime_object.png diff --git a/pygmt/tests/baseline/test_solar_set_terminator_datetime_string.png.dvc b/pygmt/tests/baseline/test_solar_set_terminator_datetime_string.png.dvc new file mode 100644 index 00000000000..a35368bbb45 --- /dev/null +++ b/pygmt/tests/baseline/test_solar_set_terminator_datetime_string.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 9437b5fd846526c7cac12ab327a4d78f + size: 26501 + path: test_solar_set_terminator_datetime_string.png diff --git a/pygmt/tests/test_coast.py b/pygmt/tests/test_coast.py index 69635aa30f6..f1df26bfccd 100644 --- a/pygmt/tests/test_coast.py +++ b/pygmt/tests/test_coast.py @@ -1,5 +1,5 @@ """ -Tests for coast. +Tests for fig.coast. """ import pytest from pygmt import Figure @@ -14,60 +14,37 @@ def test_coast(): """ fig = Figure() fig.coast( - R="-30/30/-40/40", - J="m0.1i", - B=5, - I="1/1p,blue", - N="1/0.25p,-", - W="0.25p,white", - G="green", - S="blue", - D="c", - A=10000, + region="-30/30/-40/40", + projection="m0.1i", + frame=5, + rivers="1/1p,blue", + borders="1/0.25p,-", + shorelines="0.25p,white", + land="green", + water="blue", + resolution="c", + area_thresh=10000, ) return fig -@check_figures_equal() +@pytest.mark.mpl_image_compare def test_coast_iceland(): """ - Test passing in R as a list. + Test passing in region as a list. """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.coast(R="-30/-10/60/65", J="m1c", B="", G="p28+r100") - fig_test.coast( - region=[-30, -10, 60, 65], projection="m1c", frame=True, land="p28+r100" - ) - return fig_ref, fig_test + fig = Figure() + fig.coast(region=[-30, -10, 60, 65], projection="m1c", frame=True, land="p28+r100") + return fig -@check_figures_equal() +@pytest.mark.mpl_image_compare def test_coast_aliases(): """ Test that all aliases work. """ - fig_ref, fig_test = Figure(), Figure() - fig_ref.coast( - R="-30/30/-40/40", - J="M25c", - B="afg", - I="1/1p,black", - N="1/0.5p,-", - W="0.25p,white", - G="moccasin", - S="skyblue", - D="i", - A=1000, - L="jCM+c1+w1000k+f+l", - X="a4c", - Y="a10c", - p="135/25", - t=13, - E="MA+gred", - C="blue", - ) - fig_test.coast( + fig = Figure() + fig.coast( region=[-30, 30, -40, 40], # R projection="M25c", # J frame="afg", # B @@ -86,7 +63,7 @@ def test_coast_aliases(): dcw="MA+gred", # E lakes="blue", # C ) - return fig_ref, fig_test + return fig @pytest.mark.mpl_image_compare @@ -115,99 +92,81 @@ def test_coast_required_args(): fig.coast(region="EG") -@check_figures_equal() +@pytest.mark.mpl_image_compare def test_coast_dcw_single(): """ Test passing a single country code to dcw. """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.coast(R="-10/15/25/44", J="M15c", B="a", G="brown", E="ES+gbisque+pblue") - fig_test.coast( + fig = Figure() + fig.coast( region=[-10, 15, 25, 44], frame="a", projection="M15c", land="brown", dcw="ES+gbisque+pblue", ) - return fig_ref, fig_test + return fig -@check_figures_equal() +@pytest.mark.mpl_image_compare def test_coast_dcw_multiple(): """ Test passing multiple country code to dcw. """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.coast(R="-10/15/25/44", J="M15c", B="a", G="brown", E="ES,IT+gbisque+pblue") - fig_test.coast( + fig = Figure() + fig.coast( region=[-10, 15, 25, 44], frame="a", projection="M15c", land="brown", dcw="ES,IT+gbisque+pblue", ) - return fig_ref, fig_test + return fig -@check_figures_equal() +@pytest.mark.mpl_image_compare def test_coast_dcw_list(): """ Test passing a list of country codes and fill options to dcw. """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.coast( - R="-10/15/25/44", - J="M15c", - B="a", - G="brown", - E=["ES+gbisque+pgreen", "IT+gcyan+pblue"], - ) - fig_test.coast( + fig = Figure() + fig.coast( region=[-10, 15, 25, 44], frame="a", projection="M15c", land="brown", dcw=["ES+gbisque+pgreen", "IT+gcyan+pblue"], ) - return fig_ref, fig_test + return fig -@check_figures_equal() +@pytest.mark.mpl_image_compare def test_coast_dcw_continent(): """ Test passing a continent code to dcw. """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.coast(R="-10/15/25/44", J="M15c", B="a", G="brown", E="=AF+gbisque+pblue") - fig_test.coast( + fig = Figure() + fig.coast( region=[-10, 15, 25, 44], frame="a", projection="M15c", land="brown", dcw="=AF+gbisque+pblue", ) - return fig_ref, fig_test + return fig -@check_figures_equal() +@pytest.mark.mpl_image_compare def test_coast_dcw_state(): """ Test passing a US state code to dcw. """ - fig_ref, fig_test = Figure(), Figure() - # Use single-character arguments for the reference image - fig_ref.coast( - R="-75/-69/40/44", J="M15c", B="a", G="brown", E="US.MA+gbisque+pblue" - ) - fig_test.coast( + fig = Figure() + fig.coast( region=[-75, -69, 40, 44], frame="a", projection="M15c", land="brown", dcw="US.MA+gbisque+pblue", ) - return fig_ref, fig_test + return fig diff --git a/pygmt/tests/test_solar.py b/pygmt/tests/test_solar.py new file mode 100644 index 00000000000..adc2eb649a8 --- /dev/null +++ b/pygmt/tests/test_solar.py @@ -0,0 +1,102 @@ +""" +Tests for solar. +""" +import datetime + +import pytest +from pygmt import Figure +from pygmt.exceptions import GMTInvalidInput + + +@pytest.mark.mpl_image_compare +def test_solar_set_terminator_datetime_string(): + """ + Test passing the solar argument with the civil terminator and a datetime + string. + """ + fig = Figure() + fig.solar( + region="d", + projection="W0/15c", + frame="a", + terminator="civil", + terminator_datetime="1990-02-17 04:25:00", + ) + return fig + + +@pytest.mark.mpl_image_compare +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 + + +@pytest.mark.mpl_image_compare +def test_solar_set_terminator_datetime_object(): + """ + Test passing the solar argument with the day_night terminator and a + datetime string. + """ + fig = Figure() + terminator_datetime = datetime.datetime( + year=1990, month=2, day=17, hour=4, minute=25, second=0 + ) + 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_T_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", + )