From 396535a788ab4c53258b0e0b1a3d519b24d4d831 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 5 Mar 2022 00:45:35 +0800 Subject: [PATCH 01/15] New implemention for the meca function --- pygmt/src/meca.py | 300 ++++++++------------------------------- pygmt/tests/test_meca.py | 18 ++- 2 files changed, 69 insertions(+), 249 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 85bf48d97b7..6973471de5a 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -6,14 +6,7 @@ import pandas as pd from pygmt.clib import Session from pygmt.exceptions import GMTError, GMTInvalidInput -from pygmt.helpers import ( - build_arg_string, - data_kind, - dummy_context, - fmt_docstring, - kwargs_to_strings, - use_alias, -) +from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias def data_format_code(convention, component="full"): @@ -160,17 +153,14 @@ def meca( proportional to the magnitude. Scale defines the size for magnitude = 5 (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm) longitude: int, float, list, or 1d numpy array - Longitude(s) of event location. Ignored if `spec` is not a dictionary. - List must be the length of the number of events. Ignored if `spec` is a - DataFrame and contains a 'longitude' column. + Longitude(s) of event location. Will override the longitudes in ``spec`` + if ``spec`` is a dict or DataFrame. latitude: int, float, list, or 1d numpy array - Latitude(s) of event location. Ignored if `spec` is not a dictionary. - List must be the length of the number of events. Ignored if `spec` is a - DataFrame and contains a 'latitude' column. + Latitude(s) of event location. Will override the latitudes in ``spec`` + if ``spec`` is a dict or DataFrame. depth: int, float, list, or 1d numpy array - Depth(s) of event location in kilometers. Ignored if `spec` is not a - dictionary. List must be the length of the number of events. Ignored if - `spec` is a DataFrame and contains a 'depth' column. + Depth(s) of event location in kilometers. Will override the depths in + ``spec`` if ``spec`` is a dict or DataFrame. convention: str ``"aki"`` (Aki & Richards), ``"gcmt"`` (global CMT), ``"mt"`` (seismic moment tensor), ``"partial"`` (partial focal mechanism), or @@ -181,13 +171,13 @@ def meca( full seismic moment tensor), ``"dc"`` (the closest double couple with zero trace and zero determinant), ``"deviatoric"`` (zero trace) plot_longitude: int, float, list, or 1d numpy array - Longitude(s) at which to place beachball, only used if `spec` is a - dictionary. List must be the length of the number of events. Ignored if - `spec` is a DataFrame and contains a 'plot_longitude' column. + Longitude(s) at which to place beachball. List must be the length of the + number of events. Will override the plot_longitude in ``spec`` if + ``spec`` is a dict or DataFrame plot_latitude: int, float, list, or 1d numpy array - Latitude(s) at which to place beachball, only used if `spec` is a - dictionary. List must be the length of the number of events. Ignored if - `spec` is a DataFrame and contains a 'plot_latitude' column. + Latitude(s) at which to place beachball. List must be the length of the + number of events. Will override the plot_latideu in ``spec`` if ``spec`` + is a dict or DataFrame. offset: bool or str Offsets beachballs to the longitude, latitude specified in the last two columns of the input file or array, or by `plot_longitude` and @@ -208,48 +198,11 @@ def meca( {t} """ - # pylint warnings that need to be fixed - # pylint: disable=too-many-locals - # pylint: disable=too-many-nested-blocks - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - - def set_pointer(data_pointers, spec): - """ - Set optional parameter pointers based on DataFrame or dict, if those - parameters are present in the DataFrame or dict. - """ - for param in list(data_pointers): - if param in spec: - # set pointer based on param name - data_pointers[param] = spec[param] - - def update_pointers(data_pointers): - """ - Updates variables based on the location of data, as the following data - can be passed as parameters or it can be contained in `spec`. - """ - # update all pointers - longitude = data_pointers["longitude"] - latitude = data_pointers["latitude"] - depth = data_pointers["depth"] - plot_longitude = data_pointers["plot_longitude"] - plot_latitude = data_pointers["plot_latitude"] - return (longitude, latitude, depth, plot_longitude, plot_latitude) - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - # Check the spec and parse the data according to the specified - # convention if isinstance(spec, (dict, pd.DataFrame)): - # dicts and DataFrames are handed similarly but not identically - if (longitude is None or latitude is None or depth is None) and not isinstance( - spec, (dict, pd.DataFrame) - ): - raise GMTError("Location not fully specified.") - param_conventions = { - "AKI": ["strike", "dip", "rake", "magnitude"], - "GCMT": [ + "aki": ["strike", "dip", "rake", "magnitude"], + "gcmt": [ "strike1", "dip1", "rake1", @@ -259,9 +212,9 @@ def update_pointers(data_pointers): "mantissa", "exponent", ], - "MT": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"], - "PARTIAL": ["strike1", "dip1", "strike2", "fault_type", "magnitude"], - "PRINCIPAL_AXIS": [ + "mt": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"], + "partial": ["strike1", "dip1", "strike2", "fault_type", "magnitude"], + "pricipal_axis": [ "t_exponent", "t_azimuth", "t_plunge", @@ -274,195 +227,58 @@ def update_pointers(data_pointers): "exponent", ], } - - # to keep track of where optional parameters exist - data_pointers = { - "longitude": longitude, - "latitude": latitude, - "depth": depth, - "plot_longitude": plot_longitude, - "plot_latitude": plot_latitude, - } - - # make a DataFrame copy to check convention if it contains other params - # check if a copy is necessary - copy = False - drop_list = [] - for pointer in data_pointers: - if pointer in spec: - copy = True - drop_list.append(pointer) - if copy: - spec_conv = spec.copy() - # delete optional parameters from copy for convention check - for item in drop_list: - del spec_conv[item] - else: - spec_conv = spec - - # set convention and focal parameters based on spec convention - for conv in list(param_conventions): - if set(spec_conv) == set(param_conventions[conv]): - convention = conv.lower() - foc_params = param_conventions[conv] + # determine the convention based on dict keys + for conv, paras in param_conventions.items(): + if set(paras).issubset(set(spec.keys())): + convention = conv break - else: # if there is no convention assigned + else: raise GMTError( "Parameters in spec dictionary do not match known conventions." ) - # create a dict type pointer for easier to read code - if isinstance(spec, dict): - dict_type_pointer = list(spec.values())[0] - elif isinstance(spec, pd.DataFrame): - # use df.values as pointer for DataFrame behavior - dict_type_pointer = spec.values - - # assemble the 1D array for the case of floats and ints as values - if isinstance(dict_type_pointer, (int, float)): - # update pointers - set_pointer(data_pointers, spec) - # look for optional parameters in the right place - ( - longitude, - latitude, - depth, - plot_longitude, - plot_latitude, - ) = update_pointers(data_pointers) - - # Construct the array (order matters) - spec = [longitude, latitude, depth] + [spec[key] for key in foc_params] - - # Add in plotting options, if given, otherwise add 0s - for arg in plot_longitude, plot_latitude: - if arg is None: - spec.append(0) - else: - if "A" not in kwargs: - kwargs["A"] = True - spec.append(arg) - - # or assemble the 2D array for the case of lists as values - elif isinstance(dict_type_pointer, list): - # update pointers - set_pointer(data_pointers, spec) - # look for optional parameters in the right place - ( - longitude, - latitude, - depth, - plot_longitude, - plot_latitude, - ) = update_pointers(data_pointers) - - # before constructing the 2D array lets check that each key - # of the dict has the same quantity of values to avoid bugs - list_length = len(list(spec.values())[0]) - for value in list(spec.values()): - if len(value) != list_length: - raise GMTError( - "Unequal number of focal mechanism " - "parameters supplied in 'spec'." - ) - # lets also check the inputs for longitude, latitude, - # and depth if it is a list or array - if ( - isinstance(longitude, (list, np.ndarray)) - or isinstance(latitude, (list, np.ndarray)) - or isinstance(depth, (list, np.ndarray)) - ): - if (len(longitude) != len(latitude)) or ( - len(longitude) != len(depth) - ): - raise GMTError( - "Unequal number of focal mechanism " "locations supplied." - ) - - # values are ok, so build the 2D array - spec_array = [] - for index in range(list_length): - # Construct the array one row at a time (note that order - # matters here, hence the list comprehension!) - row = [longitude[index], latitude[index], depth[index]] + [ - spec[key][index] for key in foc_params - ] - - # Add in plotting options, if given, otherwise add 0s as - # required by GMT - for arg in plot_longitude, plot_latitude: - if arg is None: - row.append(0) - else: - if "A" not in kwargs: - kwargs["A"] = True - row.append(arg[index]) - spec_array.append(row) - spec = spec_array - - # or assemble the array for the case of pd.DataFrames - elif isinstance(dict_type_pointer, np.ndarray): - # update pointers - set_pointer(data_pointers, spec) - # look for optional parameters in the right place - ( - longitude, - latitude, - depth, - plot_longitude, - plot_latitude, - ) = update_pointers(data_pointers) - - # lets also check the inputs for longitude, latitude, and depth - # just in case the user entered different length lists - if ( - isinstance(longitude, (list, np.ndarray)) - or isinstance(latitude, (list, np.ndarray)) - or isinstance(depth, (list, np.ndarray)) - ): - if (len(longitude) != len(latitude)) or (len(longitude) != len(depth)): - raise GMTError( - "Unequal number of focal mechanism locations supplied." - ) - - # values are ok, so build the 2D array in the correct order - spec_array = [] - for index in range(len(spec)): - # Construct the array one row at a time (note that order - # matters here, hence the list comprehension!) - row = [longitude[index], latitude[index], depth[index]] + [ - spec[key][index] for key in foc_params - ] - - # Add in plotting options, if given, otherwise add 0s as - # required by GMT - for arg in plot_longitude, plot_latitude: - if arg is None: - row.append(0) - else: - if "A" not in kwargs: - kwargs["A"] = True - row.append(arg[index]) - spec_array.append(row) - spec = spec_array - - else: - raise GMTError("Parameter 'spec' contains values of an unsupported type.") + # prepare for input arrays + # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [labels] + arrays = [] + # a temporary dict for event locations + loc_spec = { + "longitude": longitude if longitude is not None else spec["longitude"], + "latitude": latitude if latitude is not None else spec["latitude"], + "depth": depth if depth is not None else spec["depth"], + } + # a temporary dict for plotting beachballs + plotloc_spec = { + "plot_longtiude": plot_longitude if plot_longitude is not None else spec.get("plot_longitude", None), + "plot_latitude": plot_latitude if plot_latitude is not None else spec.get("plot_latitude", None), + } + # reset the plotloc_spec dict to empty if no plotting locations are given + if not all(plotloc_spec.values()): + plotloc_spec = {} + + # location arrays + arrays.extend(np.atleast_1d(value) for param, value in loc_spec.items()) + # focal parameter arrays + arrays.extend( + np.atleast_1d(spec[param]) for param in param_conventions[convention] + ) + # plotting location arrays if given + arrays.extend(np.atleast_1d(value) for param, value in plotloc_spec.items()) + # TODO: label arrays + # transpose the 2D array + spec = np.atleast_2d(arrays).T + + # Convert 1d array types into 2d arrays + if isinstance(spec, np.ndarray) and spec.ndim == 1: + spec = np.atleast_2d(spec) # determine data_foramt from convection and component data_format = data_format_code(convention=convention, component=component) # Assemble -S flag kwargs["S"] = data_format + scale - - kind = data_kind(spec) with Session() as lib: - if kind == "matrix": - file_context = lib.virtualfile_from_matrix(np.atleast_2d(spec)) - elif kind == "file": - file_context = dummy_context(spec) - else: - raise GMTInvalidInput(f"Unrecognized data type: {type(spec)}") + # Choose how data will be passed into the module + file_context = lib.virtualfile_from_data(check_kind="vector", data=spec) with file_context as fname: arg_str = " ".join([fname, build_arg_string(kwargs)]) lib.call_module("meca", arg_str) diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index 0742c08b970..f8cbf28c587 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -17,7 +17,7 @@ def test_meca_spec_dictionary(): fig = Figure() # Right lateral strike slip focal mechanism fig.meca( - dict(strike=0, dip=90, rake=0, magnitude=5), + spec=dict(strike=0, dip=90, rake=0, magnitude=5), longitude=0, latitude=5, depth=0, @@ -41,7 +41,7 @@ def test_meca_spec_dict_list(): strike=[330, 350], dip=[30, 50], rake=[90, 90], magnitude=[3, 2] ) fig.meca( - focal_mechanisms, + spec=focal_mechanisms, longitude=[-124.3, -124.4], latitude=[48.1, 48.2], depth=[12.0, 11.0], @@ -71,8 +71,12 @@ def test_meca_spec_dataframe(): latitude=[48.1, 48.2], depth=[12, 11.0], ) - spec_dataframe = pd.DataFrame(data=focal_mechanisms) - fig.meca(spec_dataframe, region=[-125, -122, 47, 49], scale="2c", projection="M14c") + fig.meca( + spec=pd.DataFrame(data=focal_mechanisms), + region=[-125, -122, 47, 49], + scale="2c", + projection="M14c", + ) return fig @@ -104,7 +108,7 @@ def test_meca_spec_1d_array(): ] focal_mech_array = np.asarray(focal_mechanism) fig.meca( - focal_mech_array, + spec=focal_mech_array, convention="mt", component="full", region=[-128, -127, 40, 41], @@ -145,7 +149,7 @@ def test_meca_spec_2d_array(): ] focal_mechs_array = np.asarray(focal_mechanisms) fig.meca( - focal_mechs_array, + spec=focal_mechs_array, convention="gcmt", region=[-128, -127, 40, 41], scale="2c", @@ -169,7 +173,7 @@ def test_meca_spec_file(): temp_file.write(" ".join([str(x) for x in focal_mechanism])) # supply focal mechanisms to meca as a file fig.meca( - temp.name, + spec=temp.name, convention="mt", component="full", region=[-128, -127, 40, 41], From 7a1838ddeadfcf00b4c56fcc918eb1c2834ff81b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 11 Mar 2022 00:03:49 +0800 Subject: [PATCH 02/15] Pass pandas.DataFrame directly --- pygmt/src/meca.py | 58 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 6973471de5a..91f0e3e0ac9 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -2,6 +2,9 @@ meca - Plot focal mechanisms. """ +from contextlib import AbstractAsyncContextManager +from pickle import NEWOBJ + import numpy as np import pandas as pd from pygmt.clib import Session @@ -237,38 +240,32 @@ def meca( "Parameters in spec dictionary do not match known conventions." ) - # prepare for input arrays - # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [labels] - arrays = [] - # a temporary dict for event locations - loc_spec = { - "longitude": longitude if longitude is not None else spec["longitude"], - "latitude": latitude if latitude is not None else spec["latitude"], - "depth": depth if depth is not None else spec["depth"], - } - # a temporary dict for plotting beachballs - plotloc_spec = { - "plot_longtiude": plot_longitude if plot_longitude is not None else spec.get("plot_longitude", None), - "plot_latitude": plot_latitude if plot_latitude is not None else spec.get("plot_latitude", None), - } - # reset the plotloc_spec dict to empty if no plotting locations are given - if not all(plotloc_spec.values()): - plotloc_spec = {} + # override the values in dict/DataFrame if parameters are explicity specified + if longitude is not None: + spec["longitude"] = np.atleast_1d(longitude) + if latitude is not None: + spec["latitude"] = np.atleast_1d(latitude) + if depth is not None: + spec["depth"] = np.atleast_1d(depth) + if plot_longitude is not None: + spec["plot_longitude"] = np.atleast_1d(plot_longitude).astype(str) + if plot_latitude is not None: + spec["plot_latitude"] = np.atleast_1d(plot_latitude).astype(str) - # location arrays - arrays.extend(np.atleast_1d(value) for param, value in loc_spec.items()) - # focal parameter arrays - arrays.extend( - np.atleast_1d(spec[param]) for param in param_conventions[convention] - ) - # plotting location arrays if given - arrays.extend(np.atleast_1d(value) for param, value in plotloc_spec.items()) - # TODO: label arrays - # transpose the 2D array - spec = np.atleast_2d(arrays).T + # convert dict to DataFrame so columns can be reordered + if isinstance(spec, dict): + spec = pd.DataFrame(spec) - # Convert 1d array types into 2d arrays - if isinstance(spec, np.ndarray) and spec.ndim == 1: + # expected columns are: + # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [labels] + newcols = ["longitude", "latitude", "depth"] + param_conventions[convention] + if "plot_longitude" in spec.columns and "plot_latitude" in spec.columns: + newcols += ["plot_longitude", "plot_latitude"] + kwargs["A"] = True + # reorder columns in DataFrame + spec = spec.reindex(newcols, axis=1) + elif isinstance(spec, np.ndarray) and spec.ndim == 1: + # Convert 1d array types into 2d arrays spec = np.atleast_2d(spec) # determine data_foramt from convection and component @@ -281,4 +278,5 @@ def meca( file_context = lib.virtualfile_from_data(check_kind="vector", data=spec) with file_context as fname: arg_str = " ".join([fname, build_arg_string(kwargs)]) + print(arg_str) lib.call_module("meca", arg_str) From a1e6920b5ebf1f404ac1ae9d1b551f4acfbe9045 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 11 Mar 2022 00:17:02 +0800 Subject: [PATCH 03/15] Fix linting issues --- pygmt/src/meca.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 91f0e3e0ac9..96637e0e287 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -1,10 +1,6 @@ """ meca - Plot focal mechanisms. """ - -from contextlib import AbstractAsyncContextManager -from pickle import NEWOBJ - import numpy as np import pandas as pd from pygmt.clib import Session From 340e1b480ebcb3f709d40ec45324fd314ffc2d5e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 11 Mar 2022 00:21:46 +0800 Subject: [PATCH 04/15] Add a test for offsetting beachball --- pygmt/tests/test_meca.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index f8cbf28c587..93256963935 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -241,3 +241,23 @@ def test_meca_gcmt_convention(): frame=True, ) return fig + + +@pytest.mark.mpl_image_compare +def test_meca_dict_offset(): + """ + Test offsetting beachballs for a dict input. + """ + fig = Figure() + focal_mechanism = dict(strike=330, dip=30, rake=90, magnitude=3) + fig.basemap(region=[-125, -122, 47, 49], projection="M6c", frame=True) + fig.meca( + spec=focal_mechanism, + scale="1c", + longitude=-124, + latitude=48, + depth=12.0, + plot_longitude=-124.5, + plot_latitude=47.5, + ) + return fig From 962e28999e0bdece6946c9abcb2aca43fc709363 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 11 Mar 2022 00:25:47 +0800 Subject: [PATCH 05/15] Track test_meca_dict_offset.png --- pygmt/tests/baseline/test_meca_dict_offset.png.dvc | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pygmt/tests/baseline/test_meca_dict_offset.png.dvc diff --git a/pygmt/tests/baseline/test_meca_dict_offset.png.dvc b/pygmt/tests/baseline/test_meca_dict_offset.png.dvc new file mode 100644 index 00000000000..343e37bb960 --- /dev/null +++ b/pygmt/tests/baseline/test_meca_dict_offset.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 6f800af07e13d59e4927b499cf3d035e + size: 10027 + path: test_meca_dict_offset.png From 1dc608617e53312990815a000fce0d7e715a34f6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 11 Mar 2022 00:36:56 +0800 Subject: [PATCH 06/15] Allow specify event_name for beachballs --- pygmt/src/meca.py | 15 ++++++++++----- pygmt/tests/test_meca.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 96637e0e287..706e3265775 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -112,6 +112,7 @@ def meca( component="full", plot_longitude=None, plot_latitude=None, + event_name=None, **kwargs, ): """ @@ -136,8 +137,8 @@ def meca( are supported; these determine the convention. Dictionary may contain values for a single focal mechanism or lists of values for many focal mechanisms. A Pandas DataFrame may optionally contain columns latitude, - longitude, depth, plot_longitude, and/or plot_latitude instead of - passing them to the meca method. + longitude, depth, plot_longitude, plot_latitude, and/or event_name + instead of passing them to the meca method. - ``"aki"`` — *strike, dip, rake, magnitude* - ``"gcmt"`` — *strike1, dip1, rake1, strike2, dip2, rake2, mantissa, @@ -243,21 +244,25 @@ def meca( spec["latitude"] = np.atleast_1d(latitude) if depth is not None: spec["depth"] = np.atleast_1d(depth) - if plot_longitude is not None: + if plot_longitude is not None: # must be string type spec["plot_longitude"] = np.atleast_1d(plot_longitude).astype(str) - if plot_latitude is not None: + if plot_latitude is not None: # must be string type spec["plot_latitude"] = np.atleast_1d(plot_latitude).astype(str) + if event_name is not None: + spec["event_name"] = np.atleast_1d(event_name).astype(str) # convert dict to DataFrame so columns can be reordered if isinstance(spec, dict): spec = pd.DataFrame(spec) # expected columns are: - # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [labels] + # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [event_name] newcols = ["longitude", "latitude", "depth"] + param_conventions[convention] if "plot_longitude" in spec.columns and "plot_latitude" in spec.columns: newcols += ["plot_longitude", "plot_latitude"] kwargs["A"] = True + if "event_name" in spec.columns: + newcols += ["event_name"] # reorder columns in DataFrame spec = spec.reindex(newcols, axis=1) elif isinstance(spec, np.ndarray) and spec.ndim == 1: diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index 93256963935..ea62fe0ac49 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -261,3 +261,43 @@ def test_meca_dict_offset(): plot_latitude=47.5, ) return fig + + +@pytest.mark.mpl_image_compare +def test_meca_dict_eventname(): + """ + Test offsetting beachballs for a dict input. + """ + fig = Figure() + focal_mechanism = dict(strike=330, dip=30, rake=90, magnitude=3) + fig.basemap(region=[-125, -122, 47, 49], projection="M6c", frame=True) + fig.meca( + spec=focal_mechanism, + scale="1c", + longitude=-124, + latitude=48, + depth=12.0, + event_name="Event20220311", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_meca_dict_offset_eventname(): + """ + Test offsetting beachballs for a dict input. + """ + fig = Figure() + focal_mechanism = dict(strike=330, dip=30, rake=90, magnitude=3) + fig.basemap(region=[-125, -122, 47, 49], projection="M6c", frame=True) + fig.meca( + spec=focal_mechanism, + scale="1c", + longitude=-124, + latitude=48, + depth=12.0, + plot_longitude=-124.5, + plot_latitude=47.5, + event_name="Event20220311", + ) + return fig From ea0aada65c96900d46e89256d422793da301e9d9 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 11 Mar 2022 00:37:46 +0800 Subject: [PATCH 07/15] Add two baseline images --- pygmt/tests/baseline/test_meca_dict_eventname.png.dvc | 4 ++++ pygmt/tests/baseline/test_meca_dict_offset_eventname.png.dvc | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 pygmt/tests/baseline/test_meca_dict_eventname.png.dvc create mode 100644 pygmt/tests/baseline/test_meca_dict_offset_eventname.png.dvc diff --git a/pygmt/tests/baseline/test_meca_dict_eventname.png.dvc b/pygmt/tests/baseline/test_meca_dict_eventname.png.dvc new file mode 100644 index 00000000000..61e47357f70 --- /dev/null +++ b/pygmt/tests/baseline/test_meca_dict_eventname.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 81203f9e3a43ec235cf4b5068f928b56 + size: 10689 + path: test_meca_dict_eventname.png diff --git a/pygmt/tests/baseline/test_meca_dict_offset_eventname.png.dvc b/pygmt/tests/baseline/test_meca_dict_offset_eventname.png.dvc new file mode 100644 index 00000000000..02bf5f5d427 --- /dev/null +++ b/pygmt/tests/baseline/test_meca_dict_offset_eventname.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: 67decf4bcb2cf5cd15152e937c709858 + size: 11364 + path: test_meca_dict_offset_eventname.png From e8444effaf4d85fe02277b4fec2403075d797f60 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Sat, 9 Apr 2022 23:31:27 +0800 Subject: [PATCH 08/15] Update meca tests --- pygmt/src/meca.py | 65 +++++++++++++++++++++------------------- pygmt/tests/test_meca.py | 9 +++--- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 6b9db6e6392..67b2a465250 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -1,6 +1,8 @@ """ meca - Plot focal mechanisms. """ +from readline import insert_text + import numpy as np import pandas as pd from pygmt.clib import Session @@ -105,11 +107,11 @@ def meca( self, spec, scale, + convention=None, + component="full", longitude=None, latitude=None, depth=None, - convention=None, - component="full", plot_longitude=None, plot_latitude=None, event_name=None, @@ -120,11 +122,6 @@ def meca( Full option list at :gmt-docs:`supplements/seis/meca.html` - Note - ---- - Currently, labeling of beachballs with text strings is only supported - via providing a file to `spec` as input. - {aliases} Parameters @@ -152,15 +149,6 @@ def meca( Adjusts the scaling of the radius of the beachball, which is proportional to the magnitude. Scale defines the size for magnitude = 5 (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm) - longitude: int, float, list, or 1d numpy array - Longitude(s) of event location. Will override the longitudes in ``spec`` - if ``spec`` is a dict or DataFrame. - latitude: int, float, list, or 1d numpy array - Latitude(s) of event location. Will override the latitudes in ``spec`` - if ``spec`` is a dict or DataFrame. - depth: int, float, list, or 1d numpy array - Depth(s) of event location in kilometers. Will override the depths in - ``spec`` if ``spec`` is a dict or DataFrame. convention: str ``"aki"`` (Aki & Richards), ``"gcmt"`` (global CMT), ``"mt"`` (seismic moment tensor), ``"partial"`` (partial focal mechanism), or @@ -170,6 +158,15 @@ def meca( The component of the seismic moment tensor to plot. ``"full"`` (the full seismic moment tensor), ``"dc"`` (the closest double couple with zero trace and zero determinant), ``"deviatoric"`` (zero trace) + longitude: int, float, list, or 1d numpy array + Longitude(s) of event location. Will override the longitudes in ``spec`` + if ``spec`` is a dict or DataFrame. + latitude: int, float, list, or 1d numpy array + Latitude(s) of event location. Will override the latitudes in ``spec`` + if ``spec`` is a dict or DataFrame. + depth: int, float, list, or 1d numpy array + Depth(s) of event location in kilometers. Will override the depths in + ``spec`` if ``spec`` is a dict or DataFrame. plot_longitude: int, float, list, or 1d numpy array Longitude(s) at which to place beachball. List must be the length of the number of events. Will override the plot_longitude in ``spec`` if @@ -180,10 +177,10 @@ def meca( is a dict or DataFrame. offset: bool or str Offsets beachballs to the longitude, latitude specified in the last two - columns of the input file or array, or by `plot_longitude` and - `plot_latitude` if provided. A small circle is plotted at the initial + columns of the input file or array, or by ``plot_longitude`` and + ``plot_latitude`` if provided. A small circle is plotted at the initial location and a line connects the beachball to the circle. Specify pen - and optionally append ``+ssize`` to change the line style and/or size + and optionally append **+s**\ *size* to change the line style and/or size of the circle. no_clip : bool Does NOT skip symbols that fall outside frame boundary specified by @@ -199,7 +196,7 @@ def meca( """ kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access - if isinstance(spec, (dict, pd.DataFrame)): + if isinstance(spec, (dict, pd.DataFrame)): # spec is a dict or pd.DataFrame param_conventions = { "aki": ["strike", "dip", "rake", "magnitude"], "gcmt": [ @@ -215,13 +212,13 @@ def meca( "mt": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"], "partial": ["strike1", "dip1", "strike2", "fault_type", "magnitude"], "pricipal_axis": [ - "t_exponent", + "t_value", "t_azimuth", "t_plunge", - "n_exponent", + "n_value", "n_azimuth", "n_plunge", - "p_exponent", + "p_value", "p_azimuth", "p_plunge", "exponent", @@ -233,10 +230,13 @@ def meca( convention = conv break else: - raise GMTError( - "Parameters in spec dictionary do not match known conventions." - ) + if isinstance(spec, dict): + msg = "Keys in dict 'spec' do not match known conventions." + else: + msg = "Columns in pd.DataFrame 'spec' do not match known conventions." + raise GMTError(msg) + print(longitude, latitude, depth) # override the values in dict/DataFrame if parameters are explicity specified if longitude is not None: spec["longitude"] = np.atleast_1d(longitude) @@ -244,17 +244,20 @@ def meca( spec["latitude"] = np.atleast_1d(latitude) if depth is not None: spec["depth"] = np.atleast_1d(depth) - if plot_longitude is not None: # must be string type + if plot_longitude is not None: # must be in string type spec["plot_longitude"] = np.atleast_1d(plot_longitude).astype(str) - if plot_latitude is not None: # must be string type + if plot_latitude is not None: # must be in string type spec["plot_latitude"] = np.atleast_1d(plot_latitude).astype(str) if event_name is not None: spec["event_name"] = np.atleast_1d(event_name).astype(str) + print(spec) - # convert dict to DataFrame so columns can be reordered + # convert dict to pd.DataFrame so columns can be reordered if isinstance(spec, dict): spec = pd.DataFrame(spec) + print(spec) + # expected columns are: # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [event_name] newcols = ["longitude", "latitude", "depth"] + param_conventions[convention] @@ -266,7 +269,7 @@ def meca( # reorder columns in DataFrame spec = spec.reindex(newcols, axis=1) elif isinstance(spec, np.ndarray) and spec.ndim == 1: - # Convert 1d array types into 2d arrays + # Convert 1d array into 2d array spec = np.atleast_2d(spec) # determine data_foramt from convection and component @@ -277,5 +280,7 @@ def meca( with Session() as lib: # Choose how data will be passed into the module file_context = lib.virtualfile_from_data(check_kind="vector", data=spec) + print(spec) with file_context as fname: + print(build_arg_string(kwargs, infile=fname)) lib.call_module("meca", build_arg_string(kwargs, infile=fname)) diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index ea62fe0ac49..2c1a030b8bb 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -47,7 +47,8 @@ def test_meca_spec_dict_list(): depth=[12.0, 11.0], region=[-125, -122, 47, 49], scale="2c", - projection="M14c", + projection="M8c", + frame=True ) return fig @@ -203,9 +204,9 @@ def test_meca_loc_array(): fig.meca( focal_mechanisms, scale, - longitude, - latitude, - depth, + longitude=longitude, + latitude=latitude, + depth=depth, region=[-125, -122, 47, 49], projection="M14c", ) From ccf7b76c3c606436087a3c0c71122658826ab7d5 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 May 2022 22:49:59 +0800 Subject: [PATCH 09/15] Remove unused imports --- pygmt/src/meca.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 028be9a43da..8eaf5870255 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -1,8 +1,6 @@ """ meca - Plot focal mechanisms. """ -from readline import insert_text - import numpy as np import pandas as pd from pygmt.clib import Session @@ -282,4 +280,4 @@ def meca( file_context = lib.virtualfile_from_data(check_kind="vector", data=spec) print(spec) with file_context as fname: - lib.call_module(module="meca", args=build_arg_string(kwargs, infile=fname)) \ No newline at end of file + lib.call_module(module="meca", args=build_arg_string(kwargs, infile=fname)) From edf28c771bc582da0d49903320bbcadc91fb0529 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 May 2022 22:57:59 +0800 Subject: [PATCH 10/15] Shorten the data_format_code function --- pygmt/src/meca.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 8eaf5870255..06704eb84b3 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -10,7 +10,7 @@ def data_format_code(convention, component="full"): """ - Determine the data format code for meca -S option. + Determine the data format code for meca's -S option. See the meca() method for explanations of the parameters. @@ -52,25 +52,11 @@ def data_format_code(convention, component="full"): Invalid component 'invalid' for convention 'mt'. """ # Codes for focal mechanism formats determined by "convention" - codes1 = { - "aki": "a", - "gcmt": "c", - "partial": "p", - } - - # Codes for focal mechanism formats determined by both "convention" and - # "component" + codes1 = {"aki": "a", "gcmt": "c", "partial": "p"} + # Codes for focal mechanism formats determined by both "convention" and "component" codes2 = { - "mt": { - "deviatoric": "z", - "dc": "d", - "full": "m", - }, - "principal_axis": { - "deviatoric": "t", - "dc": "y", - "full": "x", - }, + "mt": {"deviatoric": "z", "dc": "d", "full": "m"}, + "principal_axis": {"deviatoric": "t", "dc": "y", "full": "x"}, } if convention in codes1: From 50123d6011a1f6afd97d074c7a3bfcd43e504e8b Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 11 May 2022 23:57:23 +0800 Subject: [PATCH 11/15] Rewrite the docstrings --- pygmt/src/meca.py | 122 ++++++++++++++++++++++++++++++---------------- 1 file changed, 80 insertions(+), 42 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 06704eb84b3..9c93adcbdab 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -110,62 +110,101 @@ def meca( Parameters ---------- - spec: dict, 1D array, 2D array, pd.DataFrame, or str - Either a filename containing focal mechanism parameters as columns, a - 1- or 2-D array with the same, or a dictionary. If a filename or array, - `convention` is required so we know how to interpret the - columns/entries. If a dictionary, the following combinations of keys - are supported; these determine the convention. Dictionary may contain - values for a single focal mechanism or lists of values for many focal - mechanisms. A Pandas DataFrame may optionally contain columns latitude, - longitude, depth, plot_longitude, plot_latitude, and/or event_name - instead of passing them to the meca method. + spec: str, 1D array, 2D array, dict, or pd.DataFrame + Data that contains focal mechanism parameters. - - ``"aki"`` — *strike, dip, rake, magnitude* - - ``"gcmt"`` — *strike1, dip1, rake1, strike2, dip2, rake2, mantissa, - exponent* - - ``"mt"`` — *mrr, mtt, mff, mrt, mrf, mtf, exponent* - - ``"partial"`` — *strike1, dip1, strike2, fault_type, magnitude* - - ``"principal_axis"`` — *t_exponent, t_azimuth, t_plunge, n_exponent, - n_azimuth, n_plunge, p_exponent, p_azimuth, p_plunge, exponent* + ``spec`` can be specified in either of the following types: + + - ``str``: a file name containing focal mechanism parameters as columns. + The meanings of each column is: + + - Columns 1 and 2: event longitude and latitude + - Column 3: event depth (in km) + - Columns 4 to 3+n: focal mechanism parameters. The number of columns *n* + depends on the choice of ``convection``, which will be described below. + - Columns 4+n and 5+n: longitude, latitude at which to place beachball. + Using ``0 0`` will plot the beachball at the longitude, latitude given + in columns 1 and 2. [optional and requires ``offset=True`` to take effect]. + - Text string to appear near the beach ball [optional]. + + - **1D array**: focal mechanism parameters of a single event. The meanings + of columns are the same as above. + - **2D array**: focal mechanim parameters of multiple events. The meanings + of columns are the same as above. + - dict or pd.DataFrame: The dict keys or pd.DataFrame column names determine + the focal mechanims convention. For different conventions, the following + combination of keys are allowed: + + - ``"aki"``: *strike, dip, rake, magnitude* + - ``"gcmt"``: *strike1, dip1, rake1, strike2, dip2, rake2, mantissa, exponent* + - ``"mt"``: *mrr, mtt, mff, mrt, mrf, mtf, exponent* + - ``"partial"``: *strike1, dip1, strike2, fault_type, magnitude* + - ``"principal_axis"``: *t_value, t_azimuth, t_plunge, n_value, + n_azimuth, n_plunge, p_value, p_azimuth, p_plunge, exponent* + + A dict may contain values for a single focal mechanism or lists of values + for multiple focal mechanisms. + + Both dict and pd.DataFrame may optionally contain keys/column names: + ``latitude``, ``longitude``, ``depth``, ``plot_longitude``, ``plot_latitude``, + and/or ``event_name``. + + For ``spec`` in either a str, a 1D array or a 2D array, the ``convention`` parameter + is required so we know how to interpret the columns. For ``spec`` in a dict or + a pd.DataFrame, ``convention`` is not needed and is ignored if specified. scale: str Adjusts the scaling of the radius of the beachball, which is - proportional to the magnitude. Scale defines the size for magnitude = 5 - (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm) + proportional to the magnitude. *scale* defines the size for magnitude = 5 + (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm). convention: str - ``"aki"`` (Aki & Richards), ``"gcmt"`` (global CMT), ``"mt"`` (seismic - moment tensor), ``"partial"`` (partial focal mechanism), or - ``"principal_axis"`` (principal axis). Ignored if `spec` is a - dictionary or dataframe. + Focal mechanism convention. Choose from: + - ``"aki"`` (Aki & Richards) + - ``"gcmt"`` (global CMT) + - ``"mt"`` (seismic moment tensor) + - ``"partial"`` (partial focal mechanism) + - ``"principal_axis"`` (principal axis). + + Ignored if ``spec`` is a dictionary or pd.DataFrame. component: str - The component of the seismic moment tensor to plot. ``"full"`` (the - full seismic moment tensor), ``"dc"`` (the closest double couple with - zero trace and zero determinant), ``"deviatoric"`` (zero trace) + The component of the seismic moment tensor to plot. + + - ``"full"``: the full seismic moment tensor + - ``"dc"``: the closest double couple defined from the moment tensor + (zero trace and zero determinant) + - ``"deviatoric"``: deviatoric part of the moment tensor (zero trace) longitude: int, float, list, or 1d numpy array - Longitude(s) of event location. Will override the longitudes in ``spec`` - if ``spec`` is a dict or DataFrame. + Longitude(s) of event location. Must be the same length as the + number of events. Will override the ``longitude`` values + in ``spec`` if ``spec`` is a dict or pd.DataFrame. latitude: int, float, list, or 1d numpy array - Latitude(s) of event location. Will override the latitudes in ``spec`` - if ``spec`` is a dict or DataFrame. + Latitude(s) of event location. Must be the same length as the + number of events. Will override the ``latitude`` values + in ``spec`` if ``spec`` is a dict or pd.DataFrame. depth: int, float, list, or 1d numpy array - Depth(s) of event location in kilometers. Will override the depths in - ``spec`` if ``spec`` is a dict or DataFrame. + Depth(s) of event location in kilometers. Must be the same length as the + number of events. Will override the ``depth`` values in ``spec`` + if ``spec`` is a dict or pd.DataFrame. plot_longitude: int, float, list, or 1d numpy array - Longitude(s) at which to place beachball. List must be the length of the - number of events. Will override the plot_longitude in ``spec`` if - ``spec`` is a dict or DataFrame + Longitude(s) at which to place beachball. Must be the same length as the + number of events. Will override the ``plot_longitude`` values in ``spec`` + if ``spec`` is a dict or pd.DataFrame. plot_latitude: int, float, list, or 1d numpy array - Latitude(s) at which to place beachball. List must be the length of the - number of events. Will override the plot_latideu in ``spec`` if ``spec`` - is a dict or DataFrame. + Latitude(s) at which to place beachball. List must be the same length as the + number of events. Will override the ``plot_latitude`` values in ``spec`` + if ``spec`` is a dict or pd.DataFrame. + event_name : str or list of str, or 1d numpy array + Text strings (e.g., event names) to appear near the beach ball. List must + be the same length as the number of events. Will override the ``event_name`` + values in ``spec`` if ``spec`` is a dict or pd.DataFrame. offset: bool or str + [**+p**\ *pen*][**+s**\ *size]. Offsets beachballs to the longitude, latitude specified in the last two columns of the input file or array, or by ``plot_longitude`` and ``plot_latitude`` if provided. A small circle is plotted at the initial - location and a line connects the beachball to the circle. Specify pen - and optionally append **+s**\ *size* to change the line style and/or size - of the circle. + location and a line connects the beachball to the circle. + Use **+s**\ *size* to set the diameter of the circle [Default is no circle]. + Use **+p**\ *pen* to set the line pen attributes [Default is 0.25p]. no_clip : bool Does NOT skip symbols that fall outside frame boundary specified by *region* [Default is False, i.e. plot symbols inside map frame only]. @@ -178,7 +217,6 @@ def meca( {p} {t} """ - kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access if isinstance(spec, (dict, pd.DataFrame)): # spec is a dict or pd.DataFrame param_conventions = { From 5152f6722e6cd8f5933839fbb0f1f051f27d2b4c Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 May 2022 00:19:08 +0800 Subject: [PATCH 12/15] Clean up codes --- pygmt/src/meca.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 9c93adcbdab..3b8dc62a001 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -101,7 +101,7 @@ def meca( event_name=None, **kwargs, ): - """ + r""" Plot focal mechanisms. Full option list at :gmt-docs:`supplements/seis/meca.html` @@ -217,6 +217,7 @@ def meca( {p} {t} """ + # pylint: disable=too-many-arguments,too-many-locals,too-many-branches kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access if isinstance(spec, (dict, pd.DataFrame)): # spec is a dict or pd.DataFrame param_conventions = { @@ -246,7 +247,7 @@ def meca( "exponent", ], } - # determine the convention based on dict keys + # determine the convention based on dict keys or pd.DataFrame column names. for conv, paras in param_conventions.items(): if set(paras).issubset(set(spec.keys())): convention = conv @@ -255,11 +256,10 @@ def meca( if isinstance(spec, dict): msg = "Keys in dict 'spec' do not match known conventions." else: - msg = "Columns in pd.DataFrame 'spec' do not match known conventions." + msg = "Column names in pd.DataFrame 'spec' do not match known conventions." raise GMTError(msg) - print(longitude, latitude, depth) - # override the values in dict/DataFrame if parameters are explicity specified + # override the values in dict/pd.DataFrame if parameters are explicity specified if longitude is not None: spec["longitude"] = np.atleast_1d(longitude) if latitude is not None: @@ -272,14 +272,11 @@ def meca( spec["plot_latitude"] = np.atleast_1d(plot_latitude).astype(str) if event_name is not None: spec["event_name"] = np.atleast_1d(event_name).astype(str) - print(spec) # convert dict to pd.DataFrame so columns can be reordered if isinstance(spec, dict): spec = pd.DataFrame(spec) - print(spec) - # expected columns are: # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [event_name] newcols = ["longitude", "latitude", "depth"] + param_conventions[convention] @@ -302,6 +299,5 @@ def meca( with Session() as lib: # Choose how data will be passed into the module file_context = lib.virtualfile_from_data(check_kind="vector", data=spec) - print(spec) with file_context as fname: lib.call_module(module="meca", args=build_arg_string(kwargs, infile=fname)) From 3de0b8719f95b58b84989d6ee03493f939c14796 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 12 May 2022 01:06:37 +0800 Subject: [PATCH 13/15] Update test test_meca_spec_dict_list to make it easy to verify its correctness --- pygmt/tests/baseline/test_meca_spec_dict_list.png.dvc | 4 ++-- pygmt/tests/test_meca.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pygmt/tests/baseline/test_meca_spec_dict_list.png.dvc b/pygmt/tests/baseline/test_meca_spec_dict_list.png.dvc index c889888a2b1..2e4920e7356 100644 --- a/pygmt/tests/baseline/test_meca_spec_dict_list.png.dvc +++ b/pygmt/tests/baseline/test_meca_spec_dict_list.png.dvc @@ -1,4 +1,4 @@ outs: -- md5: a0abe33fb3dc4a67444cabe82cad92ba - size: 3074 +- md5: 1469301865b97d4a95bdc26a89ae7df5 + size: 13936 path: test_meca_spec_dict_list.png diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index 2c1a030b8bb..dcc6122de5f 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -42,13 +42,13 @@ def test_meca_spec_dict_list(): ) fig.meca( spec=focal_mechanisms, - longitude=[-124.3, -124.4], - latitude=[48.1, 48.2], + longitude=[-123.5, -124.5], + latitude=[47.5, 48.5], depth=[12.0, 11.0], region=[-125, -122, 47, 49], scale="2c", projection="M8c", - frame=True + frame=True, ) return fig From 134ec8342a5767ba7b70c729dd85170a9a6c4b84 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 13 May 2022 00:14:30 +0800 Subject: [PATCH 14/15] Fix formatting issues --- pygmt/src/meca.py | 93 ++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 42 deletions(-) diff --git a/pygmt/src/meca.py b/pygmt/src/meca.py index 3b8dc62a001..a44758f4037 100644 --- a/pygmt/src/meca.py +++ b/pygmt/src/meca.py @@ -53,7 +53,8 @@ def data_format_code(convention, component="full"): """ # Codes for focal mechanism formats determined by "convention" codes1 = {"aki": "a", "gcmt": "c", "partial": "p"} - # Codes for focal mechanism formats determined by both "convention" and "component" + # Codes for focal mechanism formats determined by both "convention" and + # "component" codes2 = { "mt": {"deviatoric": "z", "dc": "d", "full": "m"}, "principal_axis": {"deviatoric": "t", "dc": "y", "full": "x"}, @@ -115,48 +116,52 @@ def meca( ``spec`` can be specified in either of the following types: - - ``str``: a file name containing focal mechanism parameters as columns. - The meanings of each column is: + - ``str``: a file name containing focal mechanism parameters as + columns. The meanings of each column is: - Columns 1 and 2: event longitude and latitude - Column 3: event depth (in km) - - Columns 4 to 3+n: focal mechanism parameters. The number of columns *n* - depends on the choice of ``convection``, which will be described below. - - Columns 4+n and 5+n: longitude, latitude at which to place beachball. - Using ``0 0`` will plot the beachball at the longitude, latitude given - in columns 1 and 2. [optional and requires ``offset=True`` to take effect]. + - Columns 4 to 3+n: focal mechanism parameters. The number of columns + *n* depends on the choice of ``convection``, which will be + described below. + - Columns 4+n and 5+n: longitude, latitude at which to place + beachball. Using ``0 0`` will plot the beachball at the longitude, + latitude given in columns 1 and 2. [optional and requires + ``offset=True`` to take effect]. - Text string to appear near the beach ball [optional]. - - **1D array**: focal mechanism parameters of a single event. The meanings - of columns are the same as above. - - **2D array**: focal mechanim parameters of multiple events. The meanings - of columns are the same as above. - - dict or pd.DataFrame: The dict keys or pd.DataFrame column names determine - the focal mechanims convention. For different conventions, the following - combination of keys are allowed: + - **1D array**: focal mechanism parameters of a single event. + The meanings of columns are the same as above. + - **2D array**: focal mechanim parameters of multiple events. + The meanings of columns are the same as above. + - dict or pd.DataFrame: The dict keys or pd.DataFrame column names + determine the focal mechanims convention. For different conventions, + the following combination of keys are allowed: - ``"aki"``: *strike, dip, rake, magnitude* - - ``"gcmt"``: *strike1, dip1, rake1, strike2, dip2, rake2, mantissa, exponent* + - ``"gcmt"``: *strike1, dip1, rake1, strike2, dip2, rake2, mantissa,* + *exponent* - ``"mt"``: *mrr, mtt, mff, mrt, mrf, mtf, exponent* - ``"partial"``: *strike1, dip1, strike2, fault_type, magnitude* - ``"principal_axis"``: *t_value, t_azimuth, t_plunge, n_value, n_azimuth, n_plunge, p_value, p_azimuth, p_plunge, exponent* - A dict may contain values for a single focal mechanism or lists of values - for multiple focal mechanisms. + A dict may contain values for a single focal mechanism or lists of + values for multiple focal mechanisms. Both dict and pd.DataFrame may optionally contain keys/column names: - ``latitude``, ``longitude``, ``depth``, ``plot_longitude``, ``plot_latitude``, - and/or ``event_name``. + ``latitude``, ``longitude``, ``depth``, ``plot_longitude``, + ``plot_latitude``, and/or ``event_name``. - For ``spec`` in either a str, a 1D array or a 2D array, the ``convention`` parameter - is required so we know how to interpret the columns. For ``spec`` in a dict or - a pd.DataFrame, ``convention`` is not needed and is ignored if specified. + For ``spec`` in either a str, a 1D array or a 2D array, the + ``convention`` parameter is required so we know how to interpret the + columns. For ``spec`` in a dict or a pd.DataFrame, ``convention`` is + not needed and is ignored if specified. scale: str Adjusts the scaling of the radius of the beachball, which is - proportional to the magnitude. *scale* defines the size for magnitude = 5 - (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm). + proportional to the magnitude. *scale* defines the size for + magnitude = 5 (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm). convention: str Focal mechanism convention. Choose from: - ``"aki"`` (Aki & Richards) @@ -182,29 +187,31 @@ def meca( number of events. Will override the ``latitude`` values in ``spec`` if ``spec`` is a dict or pd.DataFrame. depth: int, float, list, or 1d numpy array - Depth(s) of event location in kilometers. Must be the same length as the - number of events. Will override the ``depth`` values in ``spec`` + Depth(s) of event location in kilometers. Must be the same length as + the number of events. Will override the ``depth`` values in ``spec`` if ``spec`` is a dict or pd.DataFrame. plot_longitude: int, float, list, or 1d numpy array - Longitude(s) at which to place beachball. Must be the same length as the - number of events. Will override the ``plot_longitude`` values in ``spec`` - if ``spec`` is a dict or pd.DataFrame. + Longitude(s) at which to place beachball. Must be the same length as + the number of events. Will override the ``plot_longitude`` values in + ``spec`` if ``spec`` is a dict or pd.DataFrame. plot_latitude: int, float, list, or 1d numpy array - Latitude(s) at which to place beachball. List must be the same length as the - number of events. Will override the ``plot_latitude`` values in ``spec`` - if ``spec`` is a dict or pd.DataFrame. + Latitude(s) at which to place beachball. List must be the same length + as the number of events. Will override the ``plot_latitude`` values in + ``spec`` if ``spec`` is a dict or pd.DataFrame. event_name : str or list of str, or 1d numpy array - Text strings (e.g., event names) to appear near the beach ball. List must - be the same length as the number of events. Will override the ``event_name`` - values in ``spec`` if ``spec`` is a dict or pd.DataFrame. + Text strings (e.g., event names) to appear near the beach ball. List + must be the same length as the number of events. Will override the + ``event_name`` values in ``spec`` if ``spec`` is a dict or + pd.DataFrame. offset: bool or str [**+p**\ *pen*][**+s**\ *size]. Offsets beachballs to the longitude, latitude specified in the last two columns of the input file or array, or by ``plot_longitude`` and ``plot_latitude`` if provided. A small circle is plotted at the initial - location and a line connects the beachball to the circle. - Use **+s**\ *size* to set the diameter of the circle [Default is no circle]. - Use **+p**\ *pen* to set the line pen attributes [Default is 0.25p]. + location and a line connects the beachball to the circle. Use + **+s**\ *size* to set the diameter of the circle [Default is + no circle]. Use **+p**\ *pen* to set the line pen attributes [Default + is 0.25p]. no_clip : bool Does NOT skip symbols that fall outside frame boundary specified by *region* [Default is False, i.e. plot symbols inside map frame only]. @@ -247,7 +254,7 @@ def meca( "exponent", ], } - # determine the convention based on dict keys or pd.DataFrame column names. + # determine convention from dict keys or pd.DataFrame column names for conv, paras in param_conventions.items(): if set(paras).issubset(set(spec.keys())): convention = conv @@ -259,7 +266,8 @@ def meca( msg = "Column names in pd.DataFrame 'spec' do not match known conventions." raise GMTError(msg) - # override the values in dict/pd.DataFrame if parameters are explicity specified + # override the values in dict/pd.DataFrame if parameters are explicity + # specified if longitude is not None: spec["longitude"] = np.atleast_1d(longitude) if latitude is not None: @@ -278,7 +286,8 @@ def meca( spec = pd.DataFrame(spec) # expected columns are: - # longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [event_name] + # longitude, latitude, depth, focal_parameters, + # [plot_longitude, plot_latitude] [event_name] newcols = ["longitude", "latitude", "depth"] + param_conventions[convention] if "plot_longitude" in spec.columns and "plot_latitude" in spec.columns: newcols += ["plot_longitude", "plot_latitude"] From a48acec44f885f529fdf5f496819444d0347f049 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 13 May 2022 23:15:44 +0800 Subject: [PATCH 15/15] Reformat test test_meca_spec_2d_array --- pygmt/tests/test_meca.py | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/pygmt/tests/test_meca.py b/pygmt/tests/test_meca.py index dcc6122de5f..3783ce2eece 100644 --- a/pygmt/tests/test_meca.py +++ b/pygmt/tests/test_meca.py @@ -130,27 +130,17 @@ def test_meca_spec_2d_array(): # the GCMT convention but the focal mechanism parameters may be # specified any of the available conventions. Since we are not using a # dict or dataframe the convention and component should be specified. - focal_mechanisms = [ + + # longitude, latitude, depth, strike1, rake1, strike2, dip2, rake2, + # mantissa, exponent, plot_longitude, plot_latitude + focal_mechanisms = np.array( [ - -127.40, # longitude - 40.87, # latitude - 12, # depth - 170, # strike1 - 20, # dip1 - -110, # rake1 - 11, # strike2 - 71, # dip2 - -83, # rake2 - 5.1, # mantissa - 23, # exponent - 0, # plot_lon, 0 means we want to plot at the event location - 0, # plot_lat - ], - [-127.50, 40.88, 12.0, 168, 40, -115, 20, 54, -70, 4.0, 23, 0, 0], - ] - focal_mechs_array = np.asarray(focal_mechanisms) + [-127.40, 40.87, 12, 170, 20, -110, 11, 71, -83, 5.1, 23, 0, 0], + [-127.50, 40.88, 12.0, 168, 40, -115, 20, 54, -70, 4.0, 23, 0, 0], + ] + ) fig.meca( - spec=focal_mechs_array, + spec=focal_mechanisms, convention="gcmt", region=[-128, -127, 40, 41], scale="2c", @@ -165,7 +155,6 @@ def test_meca_spec_file(): Test supplying a file containing focal mechanisms and locations to the spec parameter. """ - fig = Figure() focal_mechanism = [-127.43, 40.81, 12, -3.19, 1.16, 3.93, -1.02, -3.93, -1.02, 23] # writes temp file to pass to gmt