Skip to content

Commit 904553d

Browse files
seismanyvonnefroehlichweiji14
authored
Figure.savefig: Support generating GeoTIFF file (with extension '.tiff') (#2698)
Co-authored-by: Yvonne Fröhlich <[email protected]> Co-authored-by: Wei Ji <[email protected]>
1 parent 00c0b57 commit 904553d

File tree

2 files changed

+94
-13
lines changed

2 files changed

+94
-13
lines changed

pygmt/figure.py

+28-13
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,18 @@ def savefig(
257257
"""
258258
Save the figure to a file.
259259
260-
This method implements a matplotlib-like interface for
261-
:meth:`pygmt.Figure.psconvert`.
260+
Supported file formats and their extensions:
262261
263-
Supported formats: PNG (``.png``), JPEG (``.jpg`` or ``.jpeg``),
264-
PDF (``.pdf``), BMP (``.bmp``), TIFF (``.tif``), EPS (``.eps``), and
265-
KML (``.kml``). The KML output generates a companion PNG file.
262+
- PNG (``.png``)
263+
- JPEG (``.jpg`` or ``.jpeg``)
264+
- PDF (``.pdf``)
265+
- BMP (``.bmp``)
266+
- TIFF (``.tif``)
267+
- GeoTIFF (``.tiff``)
268+
- EPS (``.eps``)
269+
- KML (``.kml``)
270+
271+
For KML format, a companion PNG file is also generated.
266272
267273
You can pass in any keyword arguments that
268274
:meth:`pygmt.Figure.psconvert` accepts.
@@ -279,10 +285,10 @@ def savefig(
279285
If ``True``, will crop the figure canvas (page) to the plot area.
280286
anti_alias: bool
281287
If ``True``, will use anti-aliasing when creating raster images
282-
(PNG, JPG, TIFF). More specifically, it passes arguments ``t2``
283-
and ``g2`` to the ``anti_aliasing`` parameter of
284-
:meth:`pygmt.Figure.psconvert`. Ignored if creating vector
285-
graphics.
288+
(BMP, PNG, JPEG, TIFF, and GeoTIFF). More specifically, it passes
289+
the arguments ``"t2"`` and ``"g2"`` to the ``anti_aliasing``
290+
parameter of :meth:`pygmt.Figure.psconvert`. Ignored if creating
291+
vector graphics.
286292
show: bool
287293
If ``True``, will open the figure in an external viewer.
288294
dpi : int
@@ -301,15 +307,20 @@ def savefig(
301307
"bmp": "b",
302308
"eps": "e",
303309
"tif": "t",
310+
"tiff": None, # GeoTIFF doesn't need the -T option
304311
"kml": "g",
305312
}
306313

307314
fname = Path(fname)
308315
prefix, suffix = fname.with_suffix("").as_posix(), fname.suffix
309316
ext = suffix[1:].lower() # Remove the . and normalize to lowercase
310-
# alias jpeg to jpg
311-
if ext == "jpeg":
317+
318+
if ext == "jpeg": # Alias jpeg to jpg
312319
ext = "jpg"
320+
elif ext == "tiff": # GeoTIFF
321+
kwargs["W"] = "+g"
322+
elif ext == "kml": # KML
323+
kwargs["W"] = "+k"
313324

314325
if ext not in fmts:
315326
if ext == "ps":
@@ -328,11 +339,15 @@ def savefig(
328339
if anti_alias:
329340
kwargs["Qt"] = 2
330341
kwargs["Qg"] = 2
331-
if ext == "kml":
332-
kwargs["W"] = "+k"
333342

334343
self.psconvert(prefix=prefix, fmt=fmt, crop=crop, **kwargs)
335344

345+
# Remove the .pgw world file if exists
346+
# Not necessary after GMT 6.5.0.
347+
# See upstream fix https://github.com/GenericMappingTools/gmt/pull/7865
348+
if ext == "tiff" and fname.with_suffix(".pgw").exists():
349+
fname.with_suffix(".pgw").unlink()
350+
336351
# Rename if file extension doesn't match the input file suffix
337352
if ext != suffix[1:]:
338353
fname.with_suffix("." + ext).rename(fname)

pygmt/tests/test_figure.py

+66
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,72 @@ def test_figure_savefig_exists():
9292
fname.unlink()
9393

9494

95+
def test_figure_savefig_geotiff():
96+
"""
97+
Make sure .tif generates a normal TIFF file and .tiff generates a GeoTIFF
98+
file.
99+
"""
100+
fig = Figure()
101+
fig.basemap(region=[0, 10, 0, 10], projection="M10c", frame=True)
102+
103+
# Save as GeoTIFF
104+
geofname = Path("test_figure_savefig_geotiff.tiff")
105+
fig.savefig(geofname)
106+
assert geofname.exists()
107+
# The .pgw should not exist
108+
assert not geofname.with_suffix(".pgw").exists()
109+
110+
# Save as TIFF
111+
fname = Path("test_figure_savefig_tiff.tif")
112+
fig.savefig(fname)
113+
assert fname.exists()
114+
115+
# Check if a TIFF is georeferenced or not
116+
try:
117+
# pylint: disable=import-outside-toplevel
118+
import rioxarray
119+
from rasterio.errors import NotGeoreferencedWarning
120+
from rasterio.transform import Affine
121+
122+
# GeoTIFF
123+
with rioxarray.open_rasterio(geofname) as xds:
124+
assert xds.rio.crs is not None
125+
npt.assert_allclose(
126+
actual=xds.rio.bounds(),
127+
desired=(
128+
-661136.0621116752,
129+
-54631.82709660966,
130+
592385.4459661598,
131+
1129371.7360144067,
132+
),
133+
)
134+
assert xds.rio.shape == (1257, 1331)
135+
assert xds.rio.transform() == Affine(
136+
a=941.789262267344,
137+
b=0.0,
138+
c=-661136.0621116752,
139+
d=0.0,
140+
e=-941.92805338983,
141+
f=1129371.7360144067,
142+
)
143+
# TIFF
144+
with pytest.warns(expected_warning=NotGeoreferencedWarning) as record:
145+
with rioxarray.open_rasterio(fname) as xds:
146+
assert xds.rio.crs is None
147+
npt.assert_allclose(
148+
actual=xds.rio.bounds(), desired=(0.0, 0.0, 1331.0, 1257.0)
149+
)
150+
assert xds.rio.shape == (1257, 1331)
151+
assert xds.rio.transform() == Affine(
152+
a=1.0, b=0.0, c=0.0, d=0.0, e=1.0, f=0.0
153+
)
154+
assert len(record) == 1
155+
except ImportError:
156+
pass
157+
geofname.unlink()
158+
fname.unlink()
159+
160+
95161
def test_figure_savefig_directory_nonexists():
96162
"""
97163
Make sure that Figure.savefig() raises a FileNotFoundError when the parent

0 commit comments

Comments
 (0)