Skip to content

Commit 621330d

Browse files
committed
New implemention for the meca function
1 parent 76cb362 commit 621330d

File tree

2 files changed

+73
-249
lines changed

2 files changed

+73
-249
lines changed

pygmt/src/meca.py

+62-242
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,7 @@
66
import pandas as pd
77
from pygmt.clib import Session
88
from pygmt.exceptions import GMTError, GMTInvalidInput
9-
from pygmt.helpers import (
10-
build_arg_string,
11-
data_kind,
12-
dummy_context,
13-
fmt_docstring,
14-
kwargs_to_strings,
15-
use_alias,
16-
)
9+
from pygmt.helpers import build_arg_string, fmt_docstring, kwargs_to_strings, use_alias
1710

1811

1912
def data_format_code(convention, component="full"):
@@ -160,17 +153,14 @@ def meca(
160153
proportional to the magnitude. Scale defines the size for magnitude = 5
161154
(i.e. scalar seismic moment M0 = 4.0E23 dynes-cm)
162155
longitude: int, float, list, or 1d numpy array
163-
Longitude(s) of event location. Ignored if `spec` is not a dictionary.
164-
List must be the length of the number of events. Ignored if `spec` is a
165-
DataFrame and contains a 'longitude' column.
156+
Longitude(s) of event location. Will override the longitudes in ``spec``
157+
if ``spec`` is a dict or DataFrame.
166158
latitude: int, float, list, or 1d numpy array
167-
Latitude(s) of event location. Ignored if `spec` is not a dictionary.
168-
List must be the length of the number of events. Ignored if `spec` is a
169-
DataFrame and contains a 'latitude' column.
159+
Latitude(s) of event location. Will override the latitudes in ``spec``
160+
if ``spec`` is a dict or DataFrame.
170161
depth: int, float, list, or 1d numpy array
171-
Depth(s) of event location in kilometers. Ignored if `spec` is not a
172-
dictionary. List must be the length of the number of events. Ignored if
173-
`spec` is a DataFrame and contains a 'depth' column.
162+
Depth(s) of event location in kilometers. Will override the depths in
163+
``spec`` if ``spec`` is a dict or DataFrame.
174164
convention: str
175165
``"aki"`` (Aki & Richards), ``"gcmt"`` (global CMT), ``"mt"`` (seismic
176166
moment tensor), ``"partial"`` (partial focal mechanism), or
@@ -181,13 +171,13 @@ def meca(
181171
full seismic moment tensor), ``"dc"`` (the closest double couple with
182172
zero trace and zero determinant), ``"deviatoric"`` (zero trace)
183173
plot_longitude: int, float, list, or 1d numpy array
184-
Longitude(s) at which to place beachball, only used if `spec` is a
185-
dictionary. List must be the length of the number of events. Ignored if
186-
`spec` is a DataFrame and contains a 'plot_longitude' column.
174+
Longitude(s) at which to place beachball. List must be the length of the
175+
number of events. Will override the plot_longitude in ``spec`` if
176+
``spec`` is a dict or DataFrame
187177
plot_latitude: int, float, list, or 1d numpy array
188-
Latitude(s) at which to place beachball, only used if `spec` is a
189-
dictionary. List must be the length of the number of events. Ignored if
190-
`spec` is a DataFrame and contains a 'plot_latitude' column.
178+
Latitude(s) at which to place beachball. List must be the length of the
179+
number of events. Will override the plot_latideu in ``spec`` if ``spec``
180+
is a dict or DataFrame.
191181
offset: bool or str
192182
Offsets beachballs to the longitude, latitude specified in the last two
193183
columns of the input file or array, or by `plot_longitude` and
@@ -208,48 +198,11 @@ def meca(
208198
{t}
209199
"""
210200

211-
# pylint warnings that need to be fixed
212-
# pylint: disable=too-many-locals
213-
# pylint: disable=too-many-nested-blocks
214-
# pylint: disable=too-many-branches
215-
# pylint: disable=too-many-statements
216-
217-
def set_pointer(data_pointers, spec):
218-
"""
219-
Set optional parameter pointers based on DataFrame or dict, if those
220-
parameters are present in the DataFrame or dict.
221-
"""
222-
for param in list(data_pointers):
223-
if param in spec:
224-
# set pointer based on param name
225-
data_pointers[param] = spec[param]
226-
227-
def update_pointers(data_pointers):
228-
"""
229-
Updates variables based on the location of data, as the following data
230-
can be passed as parameters or it can be contained in `spec`.
231-
"""
232-
# update all pointers
233-
longitude = data_pointers["longitude"]
234-
latitude = data_pointers["latitude"]
235-
depth = data_pointers["depth"]
236-
plot_longitude = data_pointers["plot_longitude"]
237-
plot_latitude = data_pointers["plot_latitude"]
238-
return (longitude, latitude, depth, plot_longitude, plot_latitude)
239-
240201
kwargs = self._preprocess(**kwargs) # pylint: disable=protected-access
241-
# Check the spec and parse the data according to the specified
242-
# convention
243202
if isinstance(spec, (dict, pd.DataFrame)):
244-
# dicts and DataFrames are handed similarly but not identically
245-
if (longitude is None or latitude is None or depth is None) and not isinstance(
246-
spec, (dict, pd.DataFrame)
247-
):
248-
raise GMTError("Location not fully specified.")
249-
250203
param_conventions = {
251-
"AKI": ["strike", "dip", "rake", "magnitude"],
252-
"GCMT": [
204+
"aki": ["strike", "dip", "rake", "magnitude"],
205+
"gcmt": [
253206
"strike1",
254207
"dip1",
255208
"rake1",
@@ -259,9 +212,9 @@ def update_pointers(data_pointers):
259212
"mantissa",
260213
"exponent",
261214
],
262-
"MT": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"],
263-
"PARTIAL": ["strike1", "dip1", "strike2", "fault_type", "magnitude"],
264-
"PRINCIPAL_AXIS": [
215+
"mt": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"],
216+
"partial": ["strike1", "dip1", "strike2", "fault_type", "magnitude"],
217+
"pricipal_axis": [
265218
"t_exponent",
266219
"t_azimuth",
267220
"t_plunge",
@@ -274,195 +227,62 @@ def update_pointers(data_pointers):
274227
"exponent",
275228
],
276229
}
277-
278-
# to keep track of where optional parameters exist
279-
data_pointers = {
280-
"longitude": longitude,
281-
"latitude": latitude,
282-
"depth": depth,
283-
"plot_longitude": plot_longitude,
284-
"plot_latitude": plot_latitude,
285-
}
286-
287-
# make a DataFrame copy to check convention if it contains other params
288-
# check if a copy is necessary
289-
copy = False
290-
drop_list = []
291-
for pointer in data_pointers:
292-
if pointer in spec:
293-
copy = True
294-
drop_list.append(pointer)
295-
if copy:
296-
spec_conv = spec.copy()
297-
# delete optional parameters from copy for convention check
298-
for item in drop_list:
299-
del spec_conv[item]
300-
else:
301-
spec_conv = spec
302-
303-
# set convention and focal parameters based on spec convention
304-
for conv in list(param_conventions):
305-
if set(spec_conv) == set(param_conventions[conv]):
306-
convention = conv.lower()
307-
foc_params = param_conventions[conv]
230+
# determine the convention based on dict keys
231+
for conv, paras in param_conventions.items():
232+
if set(paras).issubset(set(spec.keys())):
233+
convention = conv
308234
break
309-
else: # if there is no convention assigned
235+
else:
310236
raise GMTError(
311237
"Parameters in spec dictionary do not match known conventions."
312238
)
313239

314-
# create a dict type pointer for easier to read code
315-
if isinstance(spec, dict):
316-
dict_type_pointer = list(spec.values())[0]
317-
elif isinstance(spec, pd.DataFrame):
318-
# use df.values as pointer for DataFrame behavior
319-
dict_type_pointer = spec.values
320-
321-
# assemble the 1D array for the case of floats and ints as values
322-
if isinstance(dict_type_pointer, (int, float)):
323-
# update pointers
324-
set_pointer(data_pointers, spec)
325-
# look for optional parameters in the right place
326-
(
327-
longitude,
328-
latitude,
329-
depth,
330-
plot_longitude,
331-
plot_latitude,
332-
) = update_pointers(data_pointers)
333-
334-
# Construct the array (order matters)
335-
spec = [longitude, latitude, depth] + [spec[key] for key in foc_params]
336-
337-
# Add in plotting options, if given, otherwise add 0s
338-
for arg in plot_longitude, plot_latitude:
339-
if arg is None:
340-
spec.append(0)
341-
else:
342-
if "A" not in kwargs:
343-
kwargs["A"] = True
344-
spec.append(arg)
345-
346-
# or assemble the 2D array for the case of lists as values
347-
elif isinstance(dict_type_pointer, list):
348-
# update pointers
349-
set_pointer(data_pointers, spec)
350-
# look for optional parameters in the right place
351-
(
352-
longitude,
353-
latitude,
354-
depth,
355-
plot_longitude,
356-
plot_latitude,
357-
) = update_pointers(data_pointers)
358-
359-
# before constructing the 2D array lets check that each key
360-
# of the dict has the same quantity of values to avoid bugs
361-
list_length = len(list(spec.values())[0])
362-
for value in list(spec.values()):
363-
if len(value) != list_length:
364-
raise GMTError(
365-
"Unequal number of focal mechanism "
366-
"parameters supplied in 'spec'."
367-
)
368-
# lets also check the inputs for longitude, latitude,
369-
# and depth if it is a list or array
370-
if (
371-
isinstance(longitude, (list, np.ndarray))
372-
or isinstance(latitude, (list, np.ndarray))
373-
or isinstance(depth, (list, np.ndarray))
374-
):
375-
if (len(longitude) != len(latitude)) or (
376-
len(longitude) != len(depth)
377-
):
378-
raise GMTError(
379-
"Unequal number of focal mechanism " "locations supplied."
380-
)
381-
382-
# values are ok, so build the 2D array
383-
spec_array = []
384-
for index in range(list_length):
385-
# Construct the array one row at a time (note that order
386-
# matters here, hence the list comprehension!)
387-
row = [longitude[index], latitude[index], depth[index]] + [
388-
spec[key][index] for key in foc_params
389-
]
390-
391-
# Add in plotting options, if given, otherwise add 0s as
392-
# required by GMT
393-
for arg in plot_longitude, plot_latitude:
394-
if arg is None:
395-
row.append(0)
396-
else:
397-
if "A" not in kwargs:
398-
kwargs["A"] = True
399-
row.append(arg[index])
400-
spec_array.append(row)
401-
spec = spec_array
402-
403-
# or assemble the array for the case of pd.DataFrames
404-
elif isinstance(dict_type_pointer, np.ndarray):
405-
# update pointers
406-
set_pointer(data_pointers, spec)
407-
# look for optional parameters in the right place
408-
(
409-
longitude,
410-
latitude,
411-
depth,
412-
plot_longitude,
413-
plot_latitude,
414-
) = update_pointers(data_pointers)
415-
416-
# lets also check the inputs for longitude, latitude, and depth
417-
# just in case the user entered different length lists
418-
if (
419-
isinstance(longitude, (list, np.ndarray))
420-
or isinstance(latitude, (list, np.ndarray))
421-
or isinstance(depth, (list, np.ndarray))
422-
):
423-
if (len(longitude) != len(latitude)) or (len(longitude) != len(depth)):
424-
raise GMTError(
425-
"Unequal number of focal mechanism locations supplied."
426-
)
427-
428-
# values are ok, so build the 2D array in the correct order
429-
spec_array = []
430-
for index in range(len(spec)):
431-
# Construct the array one row at a time (note that order
432-
# matters here, hence the list comprehension!)
433-
row = [longitude[index], latitude[index], depth[index]] + [
434-
spec[key][index] for key in foc_params
435-
]
436-
437-
# Add in plotting options, if given, otherwise add 0s as
438-
# required by GMT
439-
for arg in plot_longitude, plot_latitude:
440-
if arg is None:
441-
row.append(0)
442-
else:
443-
if "A" not in kwargs:
444-
kwargs["A"] = True
445-
row.append(arg[index])
446-
spec_array.append(row)
447-
spec = spec_array
448-
449-
else:
450-
raise GMTError("Parameter 'spec' contains values of an unsupported type.")
240+
# prepare for input arrays
241+
# longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude] [labels]
242+
arrays = []
243+
# a temporary dict for event locations
244+
loc_spec = {
245+
"longitude": longitude if longitude is not None else spec["longitude"],
246+
"latitude": latitude if latitude is not None else spec["latitude"],
247+
"depth": depth if depth is not None else spec["depth"],
248+
}
249+
# a temporary dict for plotting beachballs
250+
plotloc_spec = {
251+
"plot_longtiude": plot_longitude
252+
if plot_longitude is not None
253+
else spec.get("plot_longitude", None),
254+
"plot_latitude": plot_latitude
255+
if plot_latitude is not None
256+
else spec.get("plot_latitude", None),
257+
}
258+
# reset the plotloc_spec dict to empty if no plotting locations are given
259+
if not all(plotloc_spec.values()):
260+
plotloc_spec = {}
261+
262+
# location arrays
263+
arrays.extend(np.atleast_1d(loc_spec[param]) for param in loc_spec)
264+
# focal parameter arrays
265+
arrays.extend(
266+
np.atleast_1d(spec[param]) for param in param_conventions[convention]
267+
)
268+
# plotting location arrays if given
269+
arrays.extend(np.atleast_1d(plotloc_spec[param]) for param in plotloc_spec)
270+
# TODO: label arrays
271+
# transpose the 2D array
272+
spec = np.atleast_2d(arrays).T
273+
274+
# Convert 1d array types into 2d arrays
275+
if isinstance(spec, np.ndarray) and spec.ndim == 1:
276+
spec = np.atleast_2d(spec)
451277

452278
# determine data_foramt from convection and component
453279
data_format = data_format_code(convention=convention, component=component)
454280

455281
# Assemble -S flag
456282
kwargs["S"] = data_format + scale
457-
458-
kind = data_kind(spec)
459283
with Session() as lib:
460-
if kind == "matrix":
461-
file_context = lib.virtualfile_from_matrix(np.atleast_2d(spec))
462-
elif kind == "file":
463-
file_context = dummy_context(spec)
464-
else:
465-
raise GMTInvalidInput(f"Unrecognized data type: {type(spec)}")
284+
# Choose how data will be passed into the module
285+
file_context = lib.virtualfile_from_data(check_kind="vector", data=spec)
466286
with file_context as fname:
467287
arg_str = " ".join([fname, build_arg_string(kwargs)])
468288
lib.call_module("meca", arg_str)

0 commit comments

Comments
 (0)