I have a GeoDataFrame where I have a mix of geometry types in its geometry
column, some are multi-geometry types, some are single geometry types, some are empty geometry types, and others are None
(rare).
Let's dive into it by generating an example:
from shapely.geometry import Point, Polygon, MultiPoint
import geopandas as gpd
poly = Polygon([(-1, -1), (-1, 2), (2, 2), (2, -1), (-1, -1)])
multipoint = MultiPoint([Point(0, 0), Point(1, 1)])
point = Point(0,1)
point_ = Point(10,10)
empty_point = Point()
none_point = Point()
gdf = gpd.GeoDataFrame(geometry=[
multipoint,
point,
point_,
empty_point,
none_point
])
# To generate a POINT EMPTY:
gdf['geometry'] = gdf['geometry'].apply(lambda x: x.intersection(poly) )
gdf['geometry'].loc[4] = None # special None case
Which results in:
How to enforce the geometry column type to MultiPoint
everywhere?
Some use cases (non exhaustive) are;
- the call to the
.plot()
method on thisGeoDataFrame
. As it stands, it throws;AttributeError: 'NoneType' object has no attribute 'type'
. - to write these data to a PostGIS table where the field type is already a
Geometry(MultiPoint)
.
Based on this, I've tried the following without success:
gdf["geometry"] = [MultiPoint([x]) if \
type(x)==Point or type(x)==None or 'empty' in str(type(x)).lower() \
else x for x in gdf["geometry"]]
Returns: EmptyPartError: Can't create MultiPoint with empty component
EDIT:
To fully answer the first relevant comment below; as there actually is a POINT EMPTY
in the shown example, I expect there should also be a MULTIPOINT EMPTY
, acting as a placeholder saying that a MultiPoint
instance is created, but that it doesn't hold any information, like the POINT EMPTY
resulting from the intersection operation does.
This would make the whole series suited for writing to a Geometry(MultiPoint)
column in PostGIS and would make it possible to plot the series when some of the multipoints are occasionally empty. This logical reasoning could as well be applied to all types of geometry, of course.
EDIT bis:
I've run some extra tests with PostGIS:
- Run this SQL:
CREATE TABLE test_multipoints (
id SERIAL PRIMARY KEY,
name VARCHAR(64),
geometry Geometry(POINT,4326)
);
INSERT INTO test_multipoints (name, geometry)
VALUES ('point', 'SRID=4326;POINT(1 0)');
INSERT INTO test_multipoints (name, geometry)
VALUES ('empty_point', 'SRID=4326;POINT(1 0)');
UPDATE test_multipoints
SET geometry = null -- dropping to have an empty cell for comparison
WHERE id=2;
- From Python run this code:
import psycopg2
import geopandas as gpd
from shapely.geometry import MultiPoint, Point
params = {
'user': 'postgres',
'password': 'yourpassword',
'host': 'localhost',
'port': '5432',
'dbname': 'postgres'
}
with psycopg2.connect(**params) as conn:
sql = "SELECT id, name, geometry FROM test_multipoints;"
gdf = gpd.read_postgis(sql, conn, geom_col='geometry', crs=4326)
Now check what's inside your GeoDataFrame
:
>>> gdf
id name geometry
0 1 point POINT (1.00000 1.00000)
1 2 empty_point None
And promote it to multi using the previously described command:
>>> gdf['geometry'] = [MultiPoint([x]) if type(x)==Point \
else x for x in gdf['geometry']]
>>> gdf2
id name geometry
0 1 point MULTIPOINT (1.00000 1.00000)
1 2 empty_point None
So, maybe, GeoPandas should stick with the None type when there is no result for an operation? Because when there is an actual empty component, it throws this EmptyPartError
.