-
Notifications
You must be signed in to change notification settings - Fork 216
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
Handle geopandas and shapely geometries via geo_interface link #1000
Changes from 9 commits
28b6994
7d0a3a7
e7012d4
21b3160
2acb0ab
9ec8feb
337f7a6
329e480
a662f7d
1f9dfc4
c8eedb2
bd94abc
34de789
1ab67f3
bf72a62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
""" | ||
import os | ||
import uuid | ||
from contextlib import contextmanager | ||
from tempfile import NamedTemporaryFile | ||
|
||
import numpy as np | ||
|
@@ -104,3 +105,54 @@ def loadtxt(self, **kwargs): | |
Data read from the text file. | ||
""" | ||
return np.loadtxt(self.name, **kwargs) | ||
|
||
|
||
@contextmanager | ||
def tempfile_from_geojson(geojson): | ||
""" | ||
Saves any geo-like Python object which implements ``__geo_interface__`` | ||
(e.g. a geopandas GeoDataFrame) to a temporary OGR_GMT text file. | ||
|
||
Parameters | ||
---------- | ||
geojson : geopandas.GeoDataFrame | ||
A geopandas GeoDataFrame, or any geo-like Python object which | ||
implements __geo_interface__, i.e. a GeoJSON | ||
weiji14 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Yields | ||
------ | ||
tmpfilename : str | ||
A temporary OGR_GMT format file holding the geographical data. | ||
E.g. 'track-1a2b3c4.tsv'. | ||
""" | ||
with GMTTempFile(suffix=".gmt") as tmpfile: | ||
os.remove(tmpfile.name) # ensure file is deleted first | ||
ogrgmt_kwargs = dict(filename=tmpfile.name, driver="OGR_GMT", mode="w") | ||
try: | ||
# 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__) | ||
# Do Input/Output via Fiona virtual memory | ||
with fiona.io.MemoryFile(file_or_bytes=jsontext.encode()) as memfile: | ||
geoseries = gpd.GeoSeries.from_file(filename=memfile) | ||
geoseries.to_file(**ogrgmt_kwargs) | ||
|
||
# with memfile.open(driver="GeoJSON") as collection: | ||
# # Get schema from GeoJSON | ||
# schema = collection.schema | ||
# # Write to temporary OGR_GMT format file | ||
# with fiona.open( | ||
# fp=tmpfile.name, mode="w", driver="OGR_GMT", schema=schema | ||
# ) as ogrgmtfile: | ||
# ogrgmtfile.write(geojson.__geo_interface__) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was my failed attempt to convert a GeoJSON string to an OGR_GMT (*.gmt) format file purely using If someone can figure out a good way to solve the schema problem, that would be fantastic! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I've removed this chunk of unusable code in 34de789. Hopefully this can be revisited in the future so that GeoJSON objects can be converted into OGR_GMT purely using |
||
|
||
yield tmpfile.name |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
""" | ||
Tests on integration with geopandas. | ||
""" | ||
import numpy.testing as npt | ||
import pytest | ||
from pygmt import info | ||
|
||
gpd = pytest.importorskip("geopandas") | ||
shapely = pytest.importorskip("shapely") | ||
|
||
|
||
@pytest.fixture(scope="module", name="gdf") | ||
def fixture_gdf(): | ||
""" | ||
Create a sample geopandas GeoDataFrame object with shapely geometries of | ||
different types. | ||
""" | ||
linestring = shapely.geometry.LineString([(20, 15), (30, 15)]) | ||
polygon = shapely.geometry.Polygon([(20, 10), (23, 10), (23, 14), (20, 14)]) | ||
multipolygon = shapely.geometry.shape( | ||
{ | ||
"type": "MultiPolygon", | ||
"coordinates": [ | ||
[ | ||
[[0, 0], [20, 0], [10, 20], [0, 0]], # Counter-clockwise | ||
[[3, 2], [10, 16], [17, 2], [3, 2]], # Clockwise | ||
], | ||
[[[6, 4], [14, 4], [10, 12], [6, 4]]], # Counter-clockwise | ||
[[[25, 5], [30, 10], [35, 5], [25, 5]]], | ||
], | ||
} | ||
) | ||
# Multipolygon first so the OGR_GMT file has @GMULTIPOLYGON in the header | ||
gdf = gpd.GeoDataFrame( | ||
index=["multipolygon", "polygon", "linestring"], | ||
geometry=[multipolygon, polygon, linestring], | ||
) | ||
|
||
return gdf | ||
|
||
|
||
def test_geopandas_info_geodataframe(gdf): | ||
""" | ||
Check that info can return the bounding box region from a | ||
geopandas.GeoDataFrame. | ||
""" | ||
output = info(table=gdf, per_column=True) | ||
npt.assert_allclose(actual=output, desired=[0.0, 35.0, 0.0, 20.0]) | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"geomtype,desired", | ||
[ | ||
("multipolygon", [0.0, 35.0, 0.0, 20.0]), | ||
("polygon", [20.0, 23.0, 10.0, 14.0]), | ||
("linestring", [20.0, 30.0, 15.0, 15.0]), | ||
], | ||
) | ||
def test_geopandas_info_shapely(gdf, geomtype, desired): | ||
""" | ||
Check that info can return the bounding box region from a shapely.geometry | ||
object that has a __geo_interface__ property. | ||
""" | ||
geom = gdf.loc[geomtype].geometry | ||
output = info(table=geom, per_column=True) | ||
npt.assert_allclose(actual=output, desired=desired) |
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.
What's the reason for this?
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.
Since geopandas is an optional dependency, probably good to have a test matrix that doesn't include it.