diff --git a/doc/api/index.rst b/doc/api/index.rst index 7828a225652..7fb5b8baa8c 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -31,6 +31,7 @@ Plotting map elements Figure.inset Figure.legend Figure.logo + Figure.magnetic_rose Figure.solar Figure.text Figure.timestamp diff --git a/pygmt/figure.py b/pygmt/figure.py index 474cd91179b..4a5ebcf7c7a 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -444,6 +444,7 @@ def _repr_html_(self) -> str: inset, legend, logo, + magnetic_rose, meca, plot, plot3d, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 8905124f917..61eebccb6a4 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -35,6 +35,7 @@ from pygmt.src.inset import inset from pygmt.src.legend import legend from pygmt.src.logo import logo +from pygmt.src.magnetic_rose import magnetic_rose from pygmt.src.makecpt import makecpt from pygmt.src.meca import meca from pygmt.src.nearneighbor import nearneighbor diff --git a/pygmt/src/basemap.py b/pygmt/src/basemap.py index 167d3e11ecf..1a21f3bf5e8 100644 --- a/pygmt/src/basemap.py +++ b/pygmt/src/basemap.py @@ -2,9 +2,10 @@ basemap - Plot base maps and frames. """ +import warnings from typing import Literal -from pygmt.alias import AliasSystem +from pygmt.alias import Alias, AliasSystem from pygmt.clib import Session from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias @@ -18,7 +19,6 @@ L="map_scale", F="box", Td="rose", - Tm="compass", f="coltypes", p="perspective", ) @@ -26,6 +26,7 @@ def basemap( self, projection=None, + compass: str | None = None, verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] | bool = False, panel: int | tuple[int, int] | bool = False, @@ -47,6 +48,7 @@ def basemap( {aliases} - J = projection + - Tm = compass - V = verbose - c = panel - t = transparency @@ -84,9 +86,13 @@ def basemap( rose : str Draw a map directional rose on the map at the location defined by the reference and anchor points. - compass : str - Draw a map magnetic rose on the map at the location defined by the - reference and anchor points. + compass + Draw a map magnetic rose on the map. + + .. deprecated:: 0.17.0 + + Use :py:func:`pygmt.Figure.magnetic_rose` instead. Will be removed in + v0.20.0. {verbose} {panel} {coltypes} @@ -95,7 +101,17 @@ def basemap( """ self._activate_figure() - aliasdict = AliasSystem().add_common( + if compass is not None: + warnings.warn( + "Parameter 'compass' is deprecated in v0.17.0 and will be removed in v0.20.0. " + "Use 'Figure.magnetic_rose' instead.", + DeprecationWarning, + stacklevel=2, + ) + + aliasdict = AliasSystem( + Tm=Alias(compass, name="compass"), + ).add_common( J=projection, V=verbose, c=panel, diff --git a/pygmt/src/magnetic_rose.py b/pygmt/src/magnetic_rose.py new file mode 100644 index 00000000000..5522bb47f95 --- /dev/null +++ b/pygmt/src/magnetic_rose.py @@ -0,0 +1,172 @@ +""" +magnetic_rose - Add a map magnetic rose. +""" + +from collections.abc import Sequence +from typing import Literal + +from pygmt._typing import AnchorCode +from pygmt.alias import Alias, AliasSystem +from pygmt.clib import Session +from pygmt.exceptions import GMTInvalidInput +from pygmt.helpers import build_arg_list +from pygmt.params import Box + + +def magnetic_rose( # noqa: PLR0913 + self, + position: Sequence[float | str] | AnchorCode | None = None, + position_type: Literal[ + "mapcoords", "boxcoords", "plotcoords", "inside", "outside" + ] = "plotcoords", + anchor: AnchorCode | None = None, + anchor_offset: Sequence[float | str] | None = None, + width: float | str | None = None, + labels: Sequence[str] | bool = False, + outer_pen: str | bool = False, + inner_pen: str | bool = False, + declination: float | None = None, + declination_label: str | None = None, + intervals: Sequence[float] | None = None, + box: Box | bool = False, + perspective: str | bool = False, + verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] + | bool = False, + transparency: float | None = None, +): + """ + Add a magnetic rose to the map. + + The magnetic rose is plotted at the location defined by the reference point + (specified by the **position** and *position_type** parameters) and anchor point + (specified by the **anchor** and **anchor_offset** parameters). Refer to + :doc:`/techref/reference_anchor_points` for details about the positioning. + + Parameters + ---------- + position/position_type + Specify the reference point on the map for the magnetic rose. The reference + point can be specified in five different ways, which is selected by the + **position_type** parameter. The actual reference point is then given by the + coordinates or code specified by the **position** parameter. + + The **position_type** parameter can be one of the following: + + - ``"mapcoords"``: **position** is given as (*longitude*, *latitude*) in map + coordinates. + - ``"boxcoords"``: **position** is given as (*nx*, *ny*) in normalized + coordinates, i.e., fractional coordinates between 0 and 1 in both the x and y + directions. For example, (0, 0) is the lower-left corner and (1, 1) is the + upper-right corner of the plot bounding box. + - ``"plotcoords"``: **position** is given as (x, y) in plot coordinates, i.e., + the distances in inches, centimeters, or points from the lower left plot + origin. + - ``"inside"`` or ``"outside"``: **position** is one of the nine + :doc:`2-character justification codes `, meaning + placing the reference point at specific locations, either inside or outside + the plot bounding box. + anchor + Anchor point of the magnetic rose, specified by one of the + :doc:`2-character justification codes `. + The default value depends on the **position_type** parameter. + + - ``position_type="inside"``: **anchor** defaults to the same as **position**. + - ``position_type="outside"``: **anchor** defaults to the mirror opposite of + **position**. + - Otherwise, **anchor** defaults to ``"MC"`` (middle center). + anchor_offset + *offset* or (*offset_x*, *offset_y*). + Offset the anchor point by *offset_x* and *offset_y*. If a single value *offset* + is given, *offset_y* = *offset_x* = *offset*. + width + Width of the rose in plot coordinates (append **i** (inch), **cm** + (centimeters), or **p** (points)), or append % for a size in percentage of map + width [Default is 15 %]. + labels + A sequence of four strings to label the cardinal points W,E,S,N. Use an empty + string to skip a specific label. If the north label is ``"*"``, then a north + star is plotted instead of the north label. If set to ``True``, use the default + labels ``["W", "E", "S", "N"]``. + outer_pen + Draw the outer circle of the magnetic rose, using the given pen attributes. + inner_pen + Draw the inner circle of the magnetic rose, using the given pen attributes. + declination + Magnetic declination in degrees. By default, only a geographic north is plotted. + With this parameter set, a magnetic north is also plotted. A magnetic compass + needle is drawn inside the rose to indicate the direction to magnetic north. + declination_label + Label for the magnetic compass needle. Default is to format a label based on + **declination**. To bypass the label, set to ``"-"``. + intervals + Specify the annotation and tick intervals for the geographic and magnetic + directions. It can be a seqeunce of three or six values. If three values are + given, they are used for both geographic and magnetic directions. If six values + are given, the first three are used for geographic directions and the last three + for magnetic directions. Default is ``(30, 5, 1)``. **Note**: If + :gmt-term:`MAP_EMBELLISHMENT_MODE` is ``"auto"`` and the compass size is smaller + than 2.5 cm then the interval defaults are reset to ``(90,30, 3, 45, 15, 3)``. + + Examples + -------- + >>> import pygmt + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[-10, 10, -10, 10], projection="M15c", frame=True) + >>> fig.magnetic_rose( + ... position=(-5, -5), + ... position_type="mapcoords", + ... width="4c", + ... labels=["W", "E", "S", "*"], + ... intervals=(45, 15, 3, 60, 20, 4), + ... outer_pen="1p,red", + ... inner_pen="1p,blue", + ... declination=11.5, + ... declination_label="11.5°E", + ... ) + >>> fig.show() + """ + self._activate_figure() + + if declination_label is not None and declination is None: + msg = "Parameter 'declination' must be set when 'declination_label' is set." + raise GMTInvalidInput(msg) + _dec = ( + (declination, declination_label) + if declination_label is not None + else declination + ) + + aliasdict = AliasSystem( + F=Alias(box, name="box"), + Tm=[ + Alias( + position_type, + name="position_type", + mapping={ + "mapcoords": "g", + "boxcoords": "n", + "plotcoords": "x", + "inside": "j", + "outside": "J", + }, + ), + Alias(position, name="position", sep="/", size=2), + Alias(anchor, name="anchor", prefix="+j"), + Alias(anchor_offset, name="anchor_offset", prefix="+o", sep="/", size=2), + Alias(width, name="width", prefix="+w"), + Alias(labels, name="labels", prefix="+l", sep=",", size=4), + Alias(outer_pen, name="outer_pen", prefix="+p"), + Alias(inner_pen, name="inner_pen", prefix="+i"), + Alias( + _dec, name="declination/declination_label", prefix="+d", sep="/", size=2 + ), + Alias(intervals, name="intervals", prefix="+t", sep="/", size=(3, 6)), + ], + p=Alias(perspective, name="perspective"), + ).add_common( + V=verbose, + t=transparency, + ) + + with Session() as lib: + lib.call_module(module="basemap", args=build_arg_list(aliasdict)) diff --git a/pygmt/tests/test_basemap.py b/pygmt/tests/test_basemap.py index a88f015b0e9..5bf89bbc9f5 100644 --- a/pygmt/tests/test_basemap.py +++ b/pygmt/tests/test_basemap.py @@ -106,12 +106,13 @@ def test_basemap_compass(): Create a map with a compass. """ fig = Figure() - fig.basemap( - region=[127.5, 128.5, 26, 27], - projection="H15c", - frame=True, - compass="jMC+w5c+d11.5", - ) + with pytest.warns(UserWarning, match="Parameter 'compass' is deprecated"): + fig.basemap( + region=[127.5, 128.5, 26, 27], + projection="H15c", + frame=True, + compass="jMC+w5c+d11.5", + ) return fig diff --git a/pygmt/tests/test_config.py b/pygmt/tests/test_config.py index b946abeaea9..275e110f298 100644 --- a/pygmt/tests/test_config.py +++ b/pygmt/tests/test_config.py @@ -47,8 +47,21 @@ def test_config_font_one(): """ fig = Figure() with config(FONT="8p,red"): - fig.basemap(region=[0, 9, 0, 9], projection="C3/3/9c", compass="jTL+w3c+d4.5+l") - fig.basemap(compass="jBR+w3.5c+d-4.5+l") + fig.basemap(region=[0, 9, 0, 9], projection="C3/3/9c", frame="+n") + fig.magnetic_rose( + position_type="inside", + position="TL", + width="3c", + declination=4.5, + labels=True, + ) + fig.magnetic_rose( + position_type="inside", + position="BR", + width="3.5c", + declination=-4.5, + labels=True, + ) return fig @@ -60,8 +73,21 @@ def test_config_font_annot(): """ fig = Figure() with config(FONT_ANNOT="6p,red"): - fig.basemap(region=[0, 9, 0, 9], projection="C3/3/9c", compass="jTL+w3c+d4.5") - fig.basemap(compass="jBR+w3.5c+d-4.5") + fig.basemap(region=[0, 9, 0, 9], projection="C3/3/9c", frame="+n") + fig.magnetic_rose( + position_type="inside", + position="TL", + width="3c", + declination=4.5, + labels=True, + ) + fig.magnetic_rose( + position_type="inside", + position="BR", + width="3.5c", + declination=-4.5, + labels=True, + ) return fig diff --git a/pygmt/tests/test_magnetic_rose.py b/pygmt/tests/test_magnetic_rose.py new file mode 100644 index 00000000000..922fd156f1b --- /dev/null +++ b/pygmt/tests/test_magnetic_rose.py @@ -0,0 +1,19 @@ +""" +Test Figure.magnetic_rose. +""" + +import pytest +from pygmt import Figure + + +@pytest.mark.mpl_image_compare(filename="test_basemap_compass.png") +def test_magnetic_rose(): + """ + Create a map with a compass. Modified from the test_basemap_compass test. + """ + fig = Figure() + fig.basemap(region=[127.5, 128.5, 26, 27], projection="H15c", frame=True) + fig.magnetic_rose( + position_type="inside", position="MC", width="5c", declination=11.5 + ) + return fig