Skip to content

Figure.savefig: Support generating GeoTIFF file (with extension '.tiff') #2698

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
41767a2
Figure.savefig: Support generating GeoTIFF file (with extension '.tiff')
seisman Sep 22, 2023
cd9918f
Test the .tiff extension
seisman Sep 22, 2023
f2348cc
GeoTIFF doesn't need the -T option
seisman Sep 22, 2023
921912d
Add a new test to check if .tiff is a GeoTIFF file
seisman Sep 22, 2023
a83862f
Revert the changes in test_figure_savefig_exists
seisman Sep 22, 2023
51c8dbe
Simplify the test
seisman Sep 22, 2023
cf771cc
Fix a linting issue
seisman Sep 22, 2023
3303ba4
Fix typos
seisman Sep 24, 2023
4fd2f50
Apply suggestions from code review
seisman Sep 24, 2023
9e77ef3
Merge branch 'main' into geotiff
seisman Sep 29, 2023
d5e6617
Merge branch 'main' into geotiff
seisman Oct 4, 2023
f1f41bf
Merge remote-tracking branch 'origin/geotiff' into geotiff
seisman Oct 4, 2023
252514b
Delete the .pgw if exists
seisman Oct 4, 2023
c4d480e
Merge branch 'main' into geotiff
seisman Oct 4, 2023
2137682
Merge branch 'main' into geotiff
seisman Oct 5, 2023
d57d8e0
Merge branch 'main' into geotiff
seisman Oct 7, 2023
44bfcfe
Improve the comment about removing .pgw file
seisman Oct 9, 2023
e7c2b36
Check bounds in the test
seisman Oct 9, 2023
9127756
Merge branch 'main' into geotiff
seisman Oct 9, 2023
26837e8
Fix typos and linting issues
seisman Oct 9, 2023
d5ececc
Merge branch 'main' into geotiff
seisman Oct 12, 2023
05ef422
Merge branch 'main' into geotiff
seisman Oct 18, 2023
71611df
Fix the bounds and shape for geotiff files
seisman Oct 18, 2023
916b4a8
Merge branch 'main' into geotiff
seisman Oct 18, 2023
6a1caa3
Check Affine transformation
seisman Oct 24, 2023
da9ca29
Merge branch 'main' into geotiff
seisman Oct 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 28 additions & 13 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,18 @@ def savefig(
"""
Save the figure to a file.

This method implements a matplotlib-like interface for
:meth:`pygmt.Figure.psconvert`.
Supported file formats and their extensions:

Supported formats: PNG (``.png``), JPEG (``.jpg`` or ``.jpeg``),
PDF (``.pdf``), BMP (``.bmp``), TIFF (``.tif``), EPS (``.eps``), and
KML (``.kml``). The KML output generates a companion PNG file.
- PNG (``.png``)
- JPEG (``.jpg`` or ``.jpeg``)
- PDF (``.pdf``)
- BMP (``.bmp``)
- TIFF (``.tif``)
- GeoTIFF (``.tiff``)
- EPS (``.eps``)
- KML (``.kml``)

For KML format, a companion PNG file is also generated.

You can pass in any keyword arguments that
:meth:`pygmt.Figure.psconvert` accepts.
Expand All @@ -279,10 +285,10 @@ def savefig(
If ``True``, will crop the figure canvas (page) to the plot area.
anti_alias: bool
If ``True``, will use anti-aliasing when creating raster images
(PNG, JPG, TIFF). More specifically, it passes arguments ``t2``
and ``g2`` to the ``anti_aliasing`` parameter of
:meth:`pygmt.Figure.psconvert`. Ignored if creating vector
graphics.
(BMP, PNG, JPEG, TIFF, and GeoTIFF). More specifically, it passes
the arguments ``"t2"`` and ``"g2"`` to the ``anti_aliasing``
parameter of :meth:`pygmt.Figure.psconvert`. Ignored if creating
vector graphics.
show: bool
If ``True``, will open the figure in an external viewer.
dpi : int
Expand All @@ -301,15 +307,20 @@ def savefig(
"bmp": "b",
"eps": "e",
"tif": "t",
"tiff": None, # GeoTIFF doesn't need the -T option
"kml": "g",
}

fname = Path(fname)
prefix, suffix = fname.with_suffix("").as_posix(), fname.suffix
ext = suffix[1:].lower() # Remove the . and normalize to lowercase
# alias jpeg to jpg
if ext == "jpeg":

if ext == "jpeg": # Alias jpeg to jpg
ext = "jpg"
elif ext == "tiff": # GeoTIFF
kwargs["W"] = "+g"
elif ext == "kml": # KML
kwargs["W"] = "+k"

if ext not in fmts:
if ext == "ps":
Expand All @@ -328,11 +339,15 @@ def savefig(
if anti_alias:
kwargs["Qt"] = 2
kwargs["Qg"] = 2
if ext == "kml":
kwargs["W"] = "+k"

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

# Remove the .pgw world file if exists
# Not necessary after GMT 6.5.0.
# See upstream fix https://github.com/GenericMappingTools/gmt/pull/7865
if ext == "tiff" and fname.with_suffix(".pgw").exists():
fname.with_suffix(".pgw").unlink()

# Rename if file extension doesn't match the input file suffix
if ext != suffix[1:]:
fname.with_suffix("." + ext).rename(fname)
Expand Down
66 changes: 66 additions & 0 deletions pygmt/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,72 @@ def test_figure_savefig_exists():
fname.unlink()


def test_figure_savefig_geotiff():
"""
Make sure .tif generates a normal TIFF file and .tiff generates a GeoTIFF
file.
"""
fig = Figure()
fig.basemap(region=[0, 10, 0, 10], projection="M10c", frame=True)

# Save as GeoTIFF
geofname = Path("test_figure_savefig_geotiff.tiff")
fig.savefig(geofname)
assert geofname.exists()
# The .pgw should not exist
assert not geofname.with_suffix(".pgw").exists()

# Save as TIFF
fname = Path("test_figure_savefig_tiff.tif")
fig.savefig(fname)
assert fname.exists()

# Check if a TIFF is georeferenced or not
try:
# pylint: disable=import-outside-toplevel
import rioxarray
from rasterio.errors import NotGeoreferencedWarning
from rasterio.transform import Affine

# GeoTIFF
with rioxarray.open_rasterio(geofname) as xds:
assert xds.rio.crs is not None
npt.assert_allclose(
actual=xds.rio.bounds(),
desired=(
-661136.0621116752,
-54631.82709660966,
592385.4459661598,
1129371.7360144067,
),
)
assert xds.rio.shape == (1257, 1331)
assert xds.rio.transform() == Affine(
a=941.789262267344,
b=0.0,
c=-661136.0621116752,
d=0.0,
e=-941.92805338983,
f=1129371.7360144067,
)
# TIFF
with pytest.warns(expected_warning=NotGeoreferencedWarning) as record:
with rioxarray.open_rasterio(fname) as xds:
assert xds.rio.crs is None
npt.assert_allclose(
actual=xds.rio.bounds(), desired=(0.0, 0.0, 1331.0, 1257.0)
)
assert xds.rio.shape == (1257, 1331)
assert xds.rio.transform() == Affine(
a=1.0, b=0.0, c=0.0, d=0.0, e=1.0, f=0.0
)
assert len(record) == 1
except ImportError:
pass
geofname.unlink()
fname.unlink()


def test_figure_savefig_directory_nonexists():
"""
Make sure that Figure.savefig() raises a FileNotFoundError when the parent
Expand Down