Skip to content

Commit 07924ca

Browse files
committed
Merge branch 'main' into refactor/virtualfile_in_extra_arrays
2 parents c199dbf + 0d31234 commit 07924ca

File tree

4 files changed

+143
-98
lines changed

4 files changed

+143
-98
lines changed

.github/workflows/ci_docs.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ jobs:
147147
mv latex/pygmt.pdf pygmt-docs.pdf
148148
cd ../..
149149
150+
- name: Upload PDF as artifact for previewing on pull requests
151+
uses: actions/upload-artifact@v4.6.1
152+
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
153+
with:
154+
name: artifact-pygmt-docs-pdf
155+
path: doc/_build/pygmt-docs.pdf
156+
150157
- name: Copy the HTML ZIP archive and PDF to the html folder for dev version
151158
run: cp -v doc/_build/pygmt-docs.zip doc/_build/pygmt-docs.pdf doc/_build/html/
152159
if: github.event_name == 'push' && matrix.os == 'ubuntu-latest'

doc/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,6 @@
252252

253253
# Options for LaTeX output.
254254
latex_engine = "xelatex"
255+
latex_documents = [
256+
(root_doc, "pygmt.tex", "The PyGMT Documentation", author, "manual", True)
257+
]

doc/minversions.md

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ myst:
1212
title: "{{path}}"
1313
doc:
1414
url: "https://www.pygmt.org/{{path}}"
15-
title: "Docs"
15+
title: "Web"
16+
html:
17+
url: "https://github.com/GenericMappingTools/pygmt/releases/download/{{path}}/pygmt-docs.zip"
18+
title: "HTML+ZIP"
19+
pdf:
20+
url: "https://github.com/GenericMappingTools/pygmt/releases/download/{{path}}/pygmt-docs.pdf"
21+
title: "PDF"
1622
---
1723

1824
# Minimum Supported Versions
@@ -38,31 +44,31 @@ drop support for core (and optional) package dependencies earlier than recommend
3844
compatibility reasons.
3945
:::
4046

41-
| PyGMT Version | GMT | Python | NumPy | pandas | Xarray |
42-
|---|---|---|---|---|---|
43-
| [Dev][]* [<doc:dev>] | {{ requires.gmt }} | {{ requires.python }} | {{ requires.numpy }} | {{ requires.pandas }} | {{ requires.xarray }} |
44-
| <tag:v0.14.2> [<doc:v0.14.2>] | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 |
45-
| <tag:v0.14.1> [<doc:v0.14.1>] | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 |
46-
| <tag:v0.14.0> [<doc:v0.14.0>] | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 |
47-
| <tag:v0.13.0> [<doc:v0.13.0>] | >=6.3.0 | >=3.10 | >=1.24 | >=1.5 | >=2022.09 |
48-
| <tag:v0.12.0> [<doc:v0.12.0>] | >=6.3.0 | >=3.10 | >=1.23 | >=1.5 | >=2022.06 |
49-
| <tag:v0.11.0> [<doc:v0.11.0>] | >=6.3.0 | >=3.9 | >=1.23 | | |
50-
| <tag:v0.10.0> [<doc:v0.10.0>] | >=6.3.0 | >=3.9 | >=1.22 | | |
51-
| <tag:v0.9.0> [<doc:v0.9.0>] | >=6.3.0 | >=3.8 | >=1.21 | | |
52-
| <tag:v0.8.0> [<doc:v0.8.0>] | >=6.3.0 | >=3.8 | >=1.20 | | |
53-
| <tag:v0.7.0> [<doc:v0.7.0>] | >=6.3.0 | >=3.8 | >=1.20 | | |
54-
| <tag:v0.6.1> [<doc:v0.6.1>] | >=6.3.0 | >=3.8 | >=1.19 | | |
55-
| <tag:v0.6.0> [<doc:v0.6.0>] | >=6.3.0 | >=3.8 | >=1.19 | | |
56-
| <tag:v0.5.0> [<doc:v0.5.0>] | >=6.2.0 | >=3.7 | >=1.18 | | |
57-
| <tag:v0.4.1> [<doc:v0.4.1>] | >=6.2.0 | >=3.7 | >=1.17 | | |
58-
| <tag:v0.4.0> [<doc:v0.4.0>] | >=6.2.0 | >=3.7 | >=1.17 | | |
59-
| <tag:v0.3.1> [<doc:v0.3.1>] | >=6.1.1 | >=3.7 | | | |
60-
| <tag:v0.3.0> [<doc:v0.3.0>] | >=6.1.1 | >=3.7 | | | |
61-
| <tag:v0.2.1> [<doc:v0.2.1>] | >=6.1.1 | >=3.6 | | | |
62-
| <tag:v0.2.0> [<doc:v0.2.0>] | >=6.1.1 | 3.6 - 3.8 | | | |
63-
| <tag:v0.1.2> [<doc:v0.1.2>] | >=6.0.0 | 3.6 - 3.8 | | | |
64-
| <tag:v0.1.1> [<doc:v0.1.1>] | >=6.0.0 | 3.6 - 3.8 | | | |
65-
| <tag:v0.1.0> [<doc:v0.1.0>] | >=6.0.0 | 3.6 - 3.8 | | | |
47+
| PyGMT Version | Documentation | GMT | Python | NumPy | pandas | Xarray |
48+
|---|---|---|---|---|---|---|
49+
| [Dev][]* | <doc:dev>, [HTML+ZIP](doc:dev/pygmt-docs.zip), [PDF](doc:dev/pygmt-docs.pdf) | {{ requires.gmt }} | {{ requires.python }} | {{ requires.numpy }} | {{ requires.pandas }} | {{ requires.xarray }} |
50+
| <tag:v0.14.2> | <doc:v0.14.2>, <html:v0.14.2> | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 |
51+
| <tag:v0.14.1> | <doc:v0.14.1>, <html:v0.14.1> | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 |
52+
| <tag:v0.14.0> | <doc:v0.14.0>, <html:v0.14.0> | >=6.4.0 | >=3.11 | >=1.25 | >=2.0 | >=2023.04 |
53+
| <tag:v0.13.0> | <doc:v0.13.0>, <html:v0.13.0> | >=6.3.0 | >=3.10 | >=1.24 | >=1.5 | >=2022.09 |
54+
| <tag:v0.12.0> | <doc:v0.12.0>, <html:v0.12.0> | >=6.3.0 | >=3.10 | >=1.23 | >=1.5 | >=2022.06 |
55+
| <tag:v0.11.0> | <doc:v0.11.0>, <html:v0.11.0> | >=6.3.0 | >=3.9 | >=1.23 | | |
56+
| <tag:v0.10.0> | <doc:v0.10.0>, <html:v0.10.0> | >=6.3.0 | >=3.9 | >=1.22 | | |
57+
| <tag:v0.9.0> | <doc:v0.9.0>, <html:v0.9.0> | >=6.3.0 | >=3.8 | >=1.21 | | |
58+
| <tag:v0.8.0> | <doc:v0.8.0>, <html:v0.8.0> | >=6.3.0 | >=3.8 | >=1.20 | | |
59+
| <tag:v0.7.0> | <doc:v0.7.0>, <html:v0.7.0> | >=6.3.0 | >=3.8 | >=1.20 | | |
60+
| <tag:v0.6.1> | <doc:v0.6.1>, <html:v0.6.1> | >=6.3.0 | >=3.8 | >=1.19 | | |
61+
| <tag:v0.6.0> | <doc:v0.6.0>, <html:v0.6.0> | >=6.3.0 | >=3.8 | >=1.19 | | |
62+
| <tag:v0.5.0> | <doc:v0.5.0>, <html:v0.5.0> | >=6.2.0 | >=3.7 | >=1.18 | | |
63+
| <tag:v0.4.1> | <doc:v0.4.1>, <html:v0.4.1> | >=6.2.0 | >=3.7 | >=1.17 | | |
64+
| <tag:v0.4.0> | <doc:v0.4.0>, <html:v0.4.0> | >=6.2.0 | >=3.7 | >=1.17 | | |
65+
| <tag:v0.3.1> | <doc:v0.3.1>, <html:v0.3.1> | >=6.1.1 | >=3.7 | | | |
66+
| <tag:v0.3.0> | <doc:v0.3.0>, <html:v0.3.0> | >=6.1.1 | >=3.7 | | | |
67+
| <tag:v0.2.1> | <doc:v0.2.1>, <html:v0.2.1> | >=6.1.1 | >=3.6 | | | |
68+
| <tag:v0.2.0> | <doc:v0.2.0>, <html:v0.2.0> | >=6.1.1 | 3.6 - 3.8 | | | |
69+
| <tag:v0.1.2> | <doc:v0.1.2>, <html:v0.1.2> | >=6.0.0 | 3.6 - 3.8 | | | |
70+
| <tag:v0.1.1> | <doc:v0.1.1>, <html:v0.1.1> | >=6.0.0 | 3.6 - 3.8 | | | |
71+
| <tag:v0.1.0> | <doc:v0.1.0>, <html:v0.1.0> | >=6.0.0 | 3.6 - 3.8 | | | |
6672

6773
*Dev reflects the main branch and is for the upcoming release.
6874

pygmt/src/meca.py

Lines changed: 101 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@
66
import pandas as pd
77
from pygmt.clib import Session
88
from pygmt.exceptions import GMTInvalidInput
9-
from pygmt.helpers import build_arg_list, fmt_docstring, kwargs_to_strings, use_alias
9+
from pygmt.helpers import (
10+
build_arg_list,
11+
data_kind,
12+
fmt_docstring,
13+
kwargs_to_strings,
14+
use_alias,
15+
)
1016
from pygmt.src._common import _FocalMechanismConvention
1117

1218

@@ -25,6 +31,82 @@ def _get_focal_convention(spec, convention, component) -> _FocalMechanismConvent
2531
return _FocalMechanismConvention(convention=convention, component=component)
2632

2733

34+
def _preprocess_spec(spec, colnames, override_cols):
35+
"""
36+
Preprocess the input data.
37+
38+
Parameters
39+
----------
40+
spec
41+
The input data to be preprocessed.
42+
colnames
43+
The minimum required column names of the input data.
44+
override_cols
45+
Dictionary of column names and values to override in the input data. Only makes
46+
sense if ``spec`` is a dict or :class:`pandas.DataFrame`.
47+
"""
48+
kind = data_kind(spec) # Determine the kind of the input data.
49+
50+
# Convert pandas.DataFrame and numpy.ndarray to dict.
51+
if isinstance(spec, pd.DataFrame):
52+
spec = {k: v.to_numpy() for k, v in spec.items()}
53+
elif isinstance(spec, np.ndarray):
54+
spec = np.atleast_2d(spec)
55+
# Optional columns that are not required by the convention. The key is the
56+
# number of extra columns, and the value is a list of optional column names.
57+
extra_cols = {
58+
0: [],
59+
1: ["event_name"],
60+
2: ["plot_longitude", "plot_latitude"],
61+
3: ["plot_longitude", "plot_latitude", "event_name"],
62+
}
63+
ndiff = spec.shape[1] - len(colnames)
64+
if ndiff not in extra_cols:
65+
msg = f"Input array must have {len(colnames)} or two/three more columns."
66+
raise GMTInvalidInput(msg)
67+
spec = dict(zip([*colnames, *extra_cols[ndiff]], spec.T, strict=False))
68+
69+
# Now, the input data is a dict or an ASCII file.
70+
if isinstance(spec, dict):
71+
# The columns can be overridden by the parameters given in the function
72+
# arguments. Only makes sense for dict/pandas.DataFrame input.
73+
if kind != "matrix" and override_cols is not None:
74+
spec.update({k: v for k, v in override_cols.items() if v is not None})
75+
# Due to the internal implementation of the meca module, we need to convert the
76+
# ``plot_longitude``, ``plot_latitude``, and ``event_name`` columns into strings
77+
# if they exist.
78+
for key in ["plot_longitude", "plot_latitude", "event_name"]:
79+
if key in spec:
80+
spec[key] = np.array(spec[key], dtype=str)
81+
82+
# Reorder columns to match convention if necessary. The expected columns are:
83+
# longitude, latitude, depth, focal_parameters, [plot_longitude, plot_latitude],
84+
# [event_name].
85+
extra_cols = []
86+
if "plot_longitude" in spec and "plot_latitude" in spec:
87+
extra_cols.extend(["plot_longitude", "plot_latitude"])
88+
if "event_name" in spec:
89+
extra_cols.append("event_name")
90+
cols = [*colnames, *extra_cols]
91+
if list(spec.keys()) != cols:
92+
spec = {k: spec[k] for k in cols}
93+
return spec
94+
95+
96+
def _auto_offset(spec) -> bool:
97+
"""
98+
Determine if offset should be set based on the input data.
99+
100+
If the input data contains ``plot_longitude`` and ``plot_latitude``, then we set the
101+
``offset`` parameter to ``True`` automatically.
102+
"""
103+
return (
104+
isinstance(spec, dict | pd.DataFrame)
105+
and "plot_longitude" in spec
106+
and "plot_latitude" in spec
107+
)
108+
109+
28110
@fmt_docstring
29111
@use_alias(
30112
A="offset",
@@ -45,7 +127,7 @@ def _get_focal_convention(spec, convention, component) -> _FocalMechanismConvent
45127
t="transparency",
46128
)
47129
@kwargs_to_strings(R="sequence", c="sequence_comma", p="sequence")
48-
def meca( # noqa: PLR0912, PLR0913
130+
def meca( # noqa: PLR0913
49131
self,
50132
spec,
51133
scale,
@@ -248,78 +330,25 @@ def meca( # noqa: PLR0912, PLR0913
248330
{transparency}
249331
"""
250332
kwargs = self._preprocess(**kwargs)
251-
252333
# Determine the focal mechanism convention from the input data or parameters.
253334
_convention = _get_focal_convention(spec, convention, component)
254-
255-
# Convert spec to pandas.DataFrame unless it's a file
256-
if isinstance(spec, dict | pd.DataFrame): # spec is a dict or pd.DataFrame
257-
# convert dict to pd.DataFrame so columns can be reordered
258-
if isinstance(spec, dict):
259-
# convert values to ndarray so pandas doesn't complain about "all
260-
# scalar values". See
261-
# https://github.com/GenericMappingTools/pygmt/pull/2174
262-
spec = pd.DataFrame(
263-
{key: np.atleast_1d(value) for key, value in spec.items()}
264-
)
265-
elif isinstance(spec, np.ndarray): # spec is a numpy array
266-
# Convert array to pd.DataFrame and assign column names
267-
spec = pd.DataFrame(np.atleast_2d(spec))
268-
colnames = ["longitude", "latitude", "depth", *_convention.params]
269-
# check if spec has the expected number of columns
270-
ncolsdiff = len(spec.columns) - len(colnames)
271-
if ncolsdiff == 0:
272-
pass
273-
elif ncolsdiff == 1:
274-
colnames += ["event_name"]
275-
elif ncolsdiff == 2:
276-
colnames += ["plot_longitude", "plot_latitude"]
277-
elif ncolsdiff == 3:
278-
colnames += ["plot_longitude", "plot_latitude", "event_name"]
279-
else:
280-
msg = (
281-
f"Input array must have {len(colnames)} to {len(colnames) + 3} columns."
282-
)
283-
raise GMTInvalidInput(msg)
284-
spec.columns = colnames
285-
286-
# Now spec is a pd.DataFrame or a file
287-
if isinstance(spec, pd.DataFrame):
288-
# override the values in pd.DataFrame if parameters are given
289-
for arg, name in [
290-
(longitude, "longitude"),
291-
(latitude, "latitude"),
292-
(depth, "depth"),
293-
(plot_longitude, "plot_longitude"),
294-
(plot_latitude, "plot_latitude"),
295-
(event_name, "event_name"),
296-
]:
297-
if arg is not None:
298-
spec[name] = np.atleast_1d(arg)
299-
300-
# Due to the internal implementation of the meca module, we need to
301-
# convert the following columns to strings if they exist
302-
if "plot_longitude" in spec.columns and "plot_latitude" in spec.columns:
303-
spec["plot_longitude"] = spec["plot_longitude"].astype(str)
304-
spec["plot_latitude"] = spec["plot_latitude"].astype(str)
305-
if "event_name" in spec.columns:
306-
spec["event_name"] = spec["event_name"].astype(str)
307-
308-
# Reorder columns in DataFrame to match convention if necessary
309-
# expected columns are:
310-
# longitude, latitude, depth, focal_parameters,
311-
# [plot_longitude, plot_latitude] [event_name]
312-
newcols = ["longitude", "latitude", "depth", *_convention.params]
313-
if "plot_longitude" in spec.columns and "plot_latitude" in spec.columns:
314-
newcols += ["plot_longitude", "plot_latitude"]
315-
if kwargs.get("A") is None:
316-
kwargs["A"] = True
317-
if "event_name" in spec.columns:
318-
newcols += ["event_name"]
319-
# reorder columns in DataFrame
320-
if spec.columns.tolist() != newcols:
321-
spec = spec.reindex(newcols, axis=1)
322-
335+
# Preprocess the input data.
336+
spec = _preprocess_spec(
337+
spec,
338+
# The minimum expected columns for the input data.
339+
colnames=["longitude", "latitude", "depth", *_convention.params],
340+
override_cols={
341+
"longitude": longitude,
342+
"latitude": latitude,
343+
"depth": depth,
344+
"plot_longitude": plot_longitude,
345+
"plot_latitude": plot_latitude,
346+
"event_name": event_name,
347+
},
348+
)
349+
# Determine the offset parameter if not provided.
350+
if kwargs.get("A") is None:
351+
kwargs["A"] = _auto_offset(spec)
323352
kwargs["S"] = f"{_convention.code}{scale}"
324353
with Session() as lib:
325354
with lib.virtualfile_in(check_kind="vector", data=spec) as vintbl:

0 commit comments

Comments
 (0)