4

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:

Resulting GeoDataFrame

How to enforce the geometry column type to MultiPoint everywhere?

Some use cases (non exhaustive) are;

  1. the call to the .plot() method on this GeoDataFrame. As it stands, it throws; AttributeError: 'NoneType' object has no attribute 'type'.
  2. 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:

  1. 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;
  1. 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.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.