-
Notifications
You must be signed in to change notification settings - Fork 224
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
Figure.savefig: Support generating GeoTIFF file (with extension '.tiff') #2698
Conversation
pygmt/tests/test_figure.py
Outdated
geofname = Path("test_figure_savefig_geotiff.tiff") | ||
fig.savefig(geofname) | ||
assert geofname.exists() | ||
assert geofname.with_suffix(".pgw").exists() # The companion world file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strange, why is a pgw file generated instead of a tfw file? See https://en.wikipedia.org/wiki/World_file#Filename_extension. A .tif or .tiff file should be linked to .tfw no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See #2658 (comment) for some tests.
Here, we call gmt psconvert -A -P -W+g
without specifying the -T
option.
Looking at the psconvert
source code (https://github.com/GenericMappingTools/gmt/blob/master/src/psconvert.c#L1008-L1010),
if (!Ctrl->T.active) { /* Set default output device if none is specified */
Ctrl->T.device = (Ctrl->W.warp) ? GS_DEV_PNG : GS_DEV_JPG; /* Lossless PNG if we are making a geotiff in the end */
}
The default format is PNG is -W+g
is used. I think GMT fist converts PS to lossless PNG, then write the world file, and calls gdal_translate
to combine the PNG and world file into a GeoTIFF file. The extension of the world file is determined from the extension of the raster file (in this case PNG), thus we have a pgw
file. This may be an upstream feature or bug.
Actually, since we already have a GeoTIFF
file, the world_file is no longer needed. Maybe we should remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, thanks for the explanation. You're right that we wouldn't need the pgw
file or any world file anyway since the GeoTIFF should already have the metadata embedded inside.
Maybe we should remove it?
Maybe best to add the code to remove the pgw
file upstream in GMT when a GeoTIFF is created, or at least rename pgw
to tfw
? On the PyGMT side, we could match the upstream implementation with GMT <= 6.4.0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe best to add the code to remove the
pgw
file upstream in GMT when a GeoTIFF is created, or at least renamepgw
totfw
?
Removing pgw
makes more sense to me. I'll open an issue report and see what the GMT team thinks about it.
Edit: upstream issue report at GenericMappingTools/gmt#7844
On the PyGMT side, we could match the upstream implementation with GMT <= 6.4.0.
Do you mean we should keep the pgw
file? I'm thinking about adding a new parameter worldfile=True
to Figure.savefig()
which generates a companion world file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the PyGMT side, we could match the upstream implementation with GMT <= 6.4.0.
Do you mean we should keep the
pgw
file? I'm thinking about adding a new parameterworldfile=True
toFigure.savefig()
which generates a companion world file.
I mean we should match whatever GMT does with the pgw
world file later in GMT 6.5.0 (e.g. delete the pgw
file), but open up that issue first. The worldfile=True
parameter sounds nice actually, this would be similar to GDAL's TFW=YES/NO
option when creating GeoTIFFs - https://gdal.org/drivers/raster/gtiff.html#creation-options
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that GenericMappingTools/gmt#7865 was merged, and the upcoming GMT 6.5.0 should delete the pgw
world file if a GeoTIFF output is requested.
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
Ping @GenericMappingTools/pygmt-maintainers for reviews |
pygmt/tests/test_figure.py
Outdated
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 | ||
|
||
# GeoTIFF | ||
with rioxarray.open_rasterio(geofname) as xds: | ||
assert xds.rio.crs is not None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The output CRS from this 10cm Mercator projection map is:
PROJCS["unknown",GEOGCS["unknown",DATUM["unknown",SPHEROID["unknown",6378137,298.257220143428]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",5],PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1,AUTHORITY["EPSG","9001"]],AXIS["Easting",EAST],AXIS["Northing",NORTH]]
I tried dragging and dropping the test_figure_savefig_geotiff.tiff
into QGIS, and the projection showed up as 'unknown' (same as rioxarray above), but it defaulted to plotting on longitude 0-10, latitude 0-10:
However, the blue gridlines (actual longitude/latitude lines from QGIS) don't match up with the GeoTIFF (from psconvert), see the top left corner around 10°N, where the black/white zebra grid markings don't line up with the blue lines.
Not sure what the default projection is that is output from psconvert, but I think we should make sure there isn't a bug somewhere. Maybe we should try a few other projections besides M10c
and see if the output is ok?
@EJFielding, could you share with us the projection (-J
) parameter you used to generate the overlay plot over Mexico at #2658 (comment)? Just wanted to know how you're aligning the grids properly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wait nevermind, I think I had a cached tiff.aux.xml
file from trying a few other projections like -JEPSG:3857
, and it was rendering the GeoTIFF in the wrong projection. Deleting all the files and resaving them seems to work better now:
There's still a 900m gap between the 9°N blue longitude line and the black/white zebra grid border, but I think that's just because of the default dpi resolution being a bit coarse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the frame make trouble here? The psconvert -W (https://docs.generic-mapping-tools.org/latest/psconvert.html) says:
Be aware, however, that different results are obtained depending on the image contents and if the -B option has been used or not. The trouble with the -B option is that it creates a frame and very likely its annotations. That introduces pixels outside the map data extent, and therefore the map extents estimation will be wrong. To avoid this problem use --MAP_FRAME_TYPE=inside option which plots all annotations and ticks inside the image and therefore does not compromise the coordinate computations. Pay attention also to the cases when the plot has any of the sides with whites only because than the algorithm will fail miserably as those whites will be eaten by the Ghostscript. In that case you really must use -B or use a slightly off-white color.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming that the -B
means -B
from grdimage/basemap/etc
and not psconvert
... I tried:
with pygmt.config(MAP_FRAME_TYPE="inside"):
fig.basemap(region=[0, 10, 0, 10], projection="M10c", frame="afg")
And it does look a bit better on the latitude (y-axis) side on the top left corner. The blue line and black lines align well on the latitude grid.
But on the longitude (x-axis) side on the bottom right, there still seems to be some offset (~900m) with the blue line not aligning with the black line (and actually, there's some offset from the 0deg latitude origin too):
Given that the world file's georeferencing starts from the top-left corner (see https://en.wikipedia.org/wiki/World_file), it kinda makes sense that the top left corner is more aligned, and the offset/distortion is greater at the bottom right corner. Not sure if there's much we can do about this though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please try to produce a figure using Figure.coast
or Figure.grdimage
without the frame
parameter? The frame still has a thickness, which may affect the boundingbox or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still having an offset without the frame:
fig = Figure()
with pygmt.config(MAP_FRAME_TYPE="inside"):
fig.coast(shorelines=True, region=[0, 10, 0, 10], projection="M10c")
# Save as GeoTIFF
geofname = Path("test_figure_savefig_geotiff.tiff")
fig.savefig(geofname)
Top-left corner
Bottom-right corner
I measured the size of one of the gray pixels and it was ~900+m, same as the offset. So it might be a case of gridline vs pixel registration (xref #487)? Though I'd think the offset would be half a pixel instead (~450m).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it might be a case of gridline vs pixel registration (xref #487)?
But this is not a grid, so can't be the gridline/pixel registration issue. Anyway, if something doesn't make sense, then it must be an upstream bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that the GeoTIFF images I was making with GMT classic looked better with the inside frame. I don't remember if having the frame outside made a difference in the geolocation.
I am not sure what the conventions are for "world" files, but I know that GDAL has the reference location for the image as the top-left corner of the top-left pixel, unless you change the registration. This is the same as "pixel" registration in GMT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The other thing to realize for checking image geolocation is that the coastline database used in GMT is something like 40 years old, and not very accurate. I would not rely on the GMT coastline to check the geolocations. In some places the GMT coastline is several hundred meters different from Google Maps or OpenStreetMap.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@weiji14 When you have time, it would be better to submit an upstream issue for more in-depth discussions about the possible misalignments. I believe we can do nothing on the PyGMT side.
In case it helps, here is the GMT6 classic mode script that I used to make the GeoTIFF map (sorry for the ugly csh scripting):
|
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An extra check for the Affine transformation, otherwise ok.
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
.tif
means a standard TIFF file, while.tiff
means a GeoTIFF file.Closes #2658.
Preview: https://pygmt-dev--2698.org.readthedocs.build/en/2698/api/generated/pygmt.Figure.savefig.html