diff --git a/pygmt/helpers/tempfile.py b/pygmt/helpers/tempfile.py index 8ac63006565..d9c132e696b 100644 --- a/pygmt/helpers/tempfile.py +++ b/pygmt/helpers/tempfile.py @@ -126,18 +126,30 @@ def tempfile_from_geojson(geojson): E.g. '1a2b3c4d5e6.gmt'. """ with GMTTempFile(suffix=".gmt") as tmpfile: + # pylint: disable=import-outside-toplevel + import geopandas as gpd + os.remove(tmpfile.name) # ensure file is deleted first ogrgmt_kwargs = {"filename": tmpfile.name, "driver": "OGR_GMT", "mode": "w"} try: + # Map int/int64 to int32 since OGR_GMT only supports 32-bit integer + # https://github.com/geopandas/geopandas/issues/967#issuecomment-842877704 + # https://github.com/GenericMappingTools/pygmt/issues/2497 + if geojson.index.name is None: + geojson.index.name = "index" + geojson = geojson.reset_index(drop=False) + schema = gpd.io.file.infer_schema(geojson) + for col, dtype in schema["properties"].items(): + if dtype in ("int", "int64"): + schema["properties"][col] = "int32" + ogrgmt_kwargs["schema"] = schema # Using geopandas.to_file to directly export to OGR_GMT format geojson.to_file(**ogrgmt_kwargs) except AttributeError: - # pylint: disable=import-outside-toplevel # Other 'geo' formats which implement __geo_interface__ import json import fiona - import geopandas as gpd with fiona.Env(): jsontext = json.dumps(geojson.__geo_interface__) diff --git a/pygmt/tests/baseline/test_geopandas_plot_int_dtypes.png.dvc b/pygmt/tests/baseline/test_geopandas_plot_int_dtypes.png.dvc new file mode 100644 index 00000000000..a2e9a9a7975 --- /dev/null +++ b/pygmt/tests/baseline/test_geopandas_plot_int_dtypes.png.dvc @@ -0,0 +1,4 @@ +outs: +- md5: c1c6eda2a88adf4d96f18f4e0c5db4d5 + size: 43582 + path: test_geopandas_plot_int_dtypes.png diff --git a/pygmt/tests/test_geopandas.py b/pygmt/tests/test_geopandas.py index 106ed798cf5..44992df76da 100644 --- a/pygmt/tests/test_geopandas.py +++ b/pygmt/tests/test_geopandas.py @@ -3,7 +3,7 @@ """ import numpy.testing as npt import pytest -from pygmt import Figure, info +from pygmt import Figure, info, makecpt, which gpd = pytest.importorskip("geopandas") shapely = pytest.importorskip("shapely") @@ -131,3 +131,54 @@ def test_geopandas_plot3d_non_default_circle(): style="c0.2c", ) return fig + + +@pytest.mark.parametrize( + "dtype", + [ + "int32", + "int64", + # Enable Int32/Int64 dtypes when geopandas>=0.13.3 is released with + # patch https://github.com/geopandas/geopandas/pull/2950 + # pd.Int32Dtype(), + # pd.Int64Dtype(), + ], +) +@pytest.mark.mpl_image_compare(filename="test_geopandas_plot_int_dtypes.png") +def test_geopandas_plot_int_dtypes(dtype): + """ + Check that plotting a geopandas GeoDataFrame with integer columns works, + including int32 and int64 (non-nullable), Int32 and Int64 (nullable). + + This is a regression test for + https://github.com/GenericMappingTools/pygmt/issues/2497 + """ + # Read shapefile in geopandas.GeoDataFrame + shapefile = which( + fname="@RidgeTest.shp @RidgeTest.shx @RidgeTest.dbf @RidgeTest.prj", + download="c", + ) + gdf = gpd.read_file(shapefile[0]) + + # Reproject geometry and change dtype of NPOINTS column + gdf["geometry"] = ( + gdf.to_crs(crs="EPSG:3857") + .buffer(distance=100000) + .to_crs(crs="OGC:CRS84") # convert to lon/lat to prevent @null in PROJ CRS + ) + gdf["NPOINTS"] = gdf.NPOINTS.astype(dtype=dtype) + + # Plot figure with three polygons colored based on NPOINTS value + fig = Figure() + makecpt(cmap="lisbon", series=[10, 60, 10], continuous=True) + fig.plot( + data=gdf, + frame=True, + pen="1p,black", + close=True, + fill="+z", + cmap=True, + aspatial="Z=NPOINTS", + ) + fig.colorbar() + return fig