geopandas: BUG: `TopologyException: found non-noded intersection` error from overlay

This question is the same, but hasn’t been answered

No because already asked and unanswered.


Does anyone have experience fixing TopologyException: found non-noded intersection error in an overlay? My understanding is that it comes from floating point errors and nearly parallel lines, but I can’t find a way to play with floating point precision tolerances in geopandas.

The usual .buffer(0) trick doesn’t seem to be helping either.

Full traceback if helpful:

TopologyException: found non-noded intersection between LINESTRING (-193712 361959, -193779 362021) and LINESTRING (-193712 361959, -193712 361959) at -193712.19705318063 361958.71668422205
---------------------------------------------------------------------------
TopologicalError                          Traceback (most recent call last)
<ipython-input-7-84d76b98e9c5> in <module>
----> 1 county_overlay = gp.overlay(county_rp, dist_rp, how="identity")

~/miniconda3/lib/python3.7/site-packages/geopandas/tools/overlay.py in overlay(df1, df2, how, make_valid, keep_geom_type)
    217             result = _overlay_union(df1, df2)
    218         elif how == "identity":
--> 219             dfunion = _overlay_union(df1, df2)
    220             result = dfunion[dfunion["__idx1"].notnull()].copy()
    221 

~/miniconda3/lib/python3.7/site-packages/geopandas/tools/overlay.py in _overlay_union(df1, df2)
    129     """
    130     dfinter = _overlay_intersection(df1, df2)
--> 131     dfsym = _overlay_symmetric_diff(df1, df2)
    132     dfunion = pd.concat([dfinter, dfsym], ignore_index=True, sort=False)
    133     # keep geometry column last

~/miniconda3/lib/python3.7/site-packages/geopandas/tools/overlay.py in _overlay_symmetric_diff(df1, df2)
     99     Overlay Symmetric Difference operation used in overlay function
    100     """
--> 101     dfdiff1 = _overlay_difference(df1, df2)
    102     dfdiff2 = _overlay_difference(df2, df1)
    103     dfdiff1["__idx1"] = range(len(dfdiff1))

~/miniconda3/lib/python3.7/site-packages/geopandas/tools/overlay.py in _overlay_difference(df1, df2)
     83     for geom, neighbours in zip(df1.geometry, sidx):
     84         new = reduce(
---> 85             lambda x, y: x.difference(y), [geom] + list(df2.geometry.iloc[neighbours])
     86         )
     87         new_g.append(new)

~/miniconda3/lib/python3.7/site-packages/geopandas/tools/overlay.py in <lambda>(x, y)
     83     for geom, neighbours in zip(df1.geometry, sidx):
     84         new = reduce(
---> 85             lambda x, y: x.difference(y), [geom] + list(df2.geometry.iloc[neighbours])
     86         )
     87         new_g.append(new)

~/miniconda3/lib/python3.7/site-packages/shapely/geometry/base.py in difference(self, other)
    670     def difference(self, other):
    671         """Returns the difference of the geometries"""
--> 672         return geom_factory(self.impl['difference'](self, other))
    673 
    674     def intersection(self, other):

~/miniconda3/lib/python3.7/site-packages/shapely/topology.py in __call__(self, this, other, *args)
     68             err = TopologicalError(
     69                     "This operation could not be performed. Reason: unknown")
---> 70             self._check_topology(err, this, other)
     71         return product
     72 

~/miniconda3/lib/python3.7/site-packages/shapely/topology.py in _check_topology(self, err, *geoms)
     37                     "Likely cause is invalidity of the geometry %s" % (
     38                         self.fn.__name__, repr(geom)))
---> 39         raise err
     40 
     41 

TopologicalError: This operation could not be performed. Reason: unknown

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Comments: 16 (13 by maintainers)

Most upvoted comments

It looks this can be corrected by reducing the precision of the geometries.

We currently have a PR in pygeos to set precision: https://github.com/pygeos/pygeos/pull/257 If possible, you may be able to check out that branch, build pygeos, and use the fix below.

It looks like this is an open issue to resolve in shapely: https://github.com/Toblerity/Shapely/issues/110

Overlap appears to function correctly after setting the precision.

>>> import pygeos as pg
>>> a.geometry = pg.set_precision(a.geometry.values.data, 1e-6)
>>> b.geometry = pg.set_precision(b.geometry.values.data, 1e-6)
>>> gp.overlay(a, b, how='identity')
     STATENAME            ID DISTRICT  ...   GISJOIN    pre_county                                           geometry
0   California  006083087001        1  ...  G0600150  2.627101e+09  MULTIPOLYGON (((-2171984.527 520516.198, -2171...
1   California  006083087002        2  ...  G0600150  2.627101e+09  MULTIPOLYGON (((-2144585.027 478787.108, -2144...
...

It may also be possible to round the coordinates in the geometries using the [apply](https://pygeos.readthedocs.io/en/latest/coordinates.html#pygeos.coordinates.apply) function in pygeos: (untested!)

>>> import numpy as np
>>>import pygeos as pg

# define a function that rounds the coordinates of every geometry in the array
>>> round = np.vectorize(lambda geom: pg.apply(geom, lambda g: g.round(3)))

>>> a.geometry = round(a.geometry.values.data)
>>> b.geometry = round(b.geometry.values.data)

The solution given by @brendan-ward did not work for me, but the following seemed to work:

import shapely.wkt
df["geometry"] = df["geometry"].apply(lambda x: shapely.wkt.loads(shapely.wkt.dumps(x, rounding_precision=4)))

Cheers!

At one point during the operation, shapely/pygeos returns invalid polygon. The next step after that fails. Sometimes it helps to round coordinates but not in this case (code for that below for someone else who may find this).

I am not sure how to fix that to be honest apart from adding intentional validity check to overlay and doing buffer(0) inside overlay. What are you trying to do? Maybe we can come up with another way.


The code for rounding:

from shapely.wkt import loads, dumps

a.geometry = [loads(dumps(geom, rounding_precision=3)) for geom in a.geometry]
b.geometry = [loads(dumps(geom, rounding_precision=3)) for geom in b.geometry]