Skip to content
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

regridding.ipynb: AttributeError: 'MultiPolygon' object has no attribute 'exterior' with upcoming Jupyter env #290

Closed
tlvu opened this issue May 23, 2023 · 1 comment · Fixed by #281

Comments

@tlvu
Copy link
Contributor

tlvu commented May 23, 2023

15:43:32  ____ pavics-sdi-fix_climex/docs/source/notebooks/regridding.ipynb::Cell 23 _____
15:43:32  Notebook cell execution failed
15:43:32  Cell 23: Cell execution caused an exception
15:43:32  
15:43:32  Input:
15:43:32  # This is only to show the decrease in size
15:43:32  def count_points(elem):
15:43:32      def _count(poly):
15:43:32          return len(poly.exterior.coords) + sum(
15:43:32              len(hole.coords) for hole in poly.interiors
15:43:32          )
15:43:32  
15:43:32      if hasattr(elem, "__len__"):  # then it is a MultiPolygon
15:43:32          return sum(_count(poly) for poly in elem)
15:43:32      return _count(elem)
15:43:32  
15:43:32  
15:43:32  # Count the total number of nodes in the shapes:
15:43:32  print(
15:43:32      "Total number of nodes in the raw shapes : ",
15:43:32      shapes.geometry.apply(count_points).sum(),
15:43:32  )
15:43:32  
15:43:32  min_grid_size = float(
15:43:32      min(abs(ds_in.lat.diff("lat")).min(), abs(ds_in.lon.diff("lon")).min())
15:43:32  )
15:43:32  print(
15:43:32      f"Minimal grid size [��] of input ds: {min_grid_size:0.3f}, we will simplify to a tolerance of {min_grid_size / 100:0.5f}"
15:43:32  )
15:43:32  
15:43:32  # Simplify geometries
15:43:32  shapes_simp = shapes.copy()
15:43:32  shapes_simp["geometry"] = shapes.simplify(min_grid_size / 100).buffer(0)
15:43:32  
15:43:32  print(
15:43:32      "Total number of nodes in the simplified shapes : ",
15:43:32      shapes_simp.geometry.apply(count_points).sum(),
15:43:32  )
15:43:32  assert shapes_simp.buffer(0).is_valid.all()
15:43:32  
15:43:32  Traceback:
15:43:32  
15:43:32  ---------------------------------------------------------------------------
15:43:32  AttributeError                            Traceback (most recent call last)
15:43:32  Cell In[24], line 16
15:43:32       10     return _count(elem)
15:43:32       13 # Count the total number of nodes in the shapes:
15:43:32       14 print(
15:43:32       15     "Total number of nodes in the raw shapes : ",
15:43:32  ---> 16     shapes.geometry.apply(count_points).sum(),
15:43:32       17 )
15:43:32       19 min_grid_size = float(
15:43:32       20     min(abs(ds_in.lat.diff("lat")).min(), abs(ds_in.lon.diff("lon")).min())
15:43:32       21 )
15:43:32       22 print(
15:43:32       23     f"Minimal grid size [��] of input ds: {min_grid_size:0.3f}, we will simplify to a tolerance of {min_grid_size / 100:0.5f}"
15:43:32       24 )
15:43:32  
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/geopandas/geoseries.py:645, in GeoSeries.apply(self, func, convert_dtype, args, **kwargs)
15:43:32      643 @doc(pd.Series)
15:43:32      644 def apply(self, func, convert_dtype=True, args=(), **kwargs):
15:43:32  --> 645     result = super().apply(func, convert_dtype=convert_dtype, args=args, **kwargs)
15:43:32      646     if isinstance(result, GeoSeries):
15:43:32      647         if self.crs is not None:
15:43:32  
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/core/series.py:4357, in Series.apply(self, func, convert_dtype, args, **kwargs)
15:43:32     4247 def apply(
15:43:32     4248     self,
15:43:32     4249     func: AggFuncType,
15:43:32     (...)
15:43:32     4252     **kwargs,
15:43:32     4253 ) -> FrameOrSeriesUnion:
15:43:32     4254     """
15:43:32     4255     Invoke function on values of Series.
15:43:32     4256 
15:43:32     (...)
15:43:32     4355     dtype: float64
15:43:32     4356     """
15:43:32  -> 4357     return SeriesApply(self, func, convert_dtype, args, kwargs).apply()
15:43:32  
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/core/apply.py:1043, in SeriesApply.apply(self)
15:43:32     1039 if isinstance(self.f, str):
15:43:32     1040     # if we are a string, try to dispatch
15:43:32     1041     return self.apply_str()
15:43:32  -> 1043 return self.apply_standard()
15:43:32  
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/core/apply.py:1098, in SeriesApply.apply_standard(self)
15:43:32     1092         values = obj.astype(object)._values
15:43:32     1093         # error: Argument 2 to "map_infer" has incompatible type
15:43:32     1094         # "Union[Callable[..., Any], str, List[Union[Callable[..., Any], str]],
15:43:32     1095         # Dict[Hashable, Union[Union[Callable[..., Any], str],
15:43:32     1096         # List[Union[Callable[..., Any], str]]]]]"; expected
15:43:32     1097         # "Callable[[Any], Any]"
15:43:32  -> 1098         mapped = lib.map_infer(
15:43:32     1099             values,
15:43:32     1100             f,  # type: ignore[arg-type]
15:43:32     1101             convert=self.convert_dtype,
15:43:32     1102         )
15:43:32     1104 if len(mapped) and isinstance(mapped[0], ABCSeries):
15:43:32     1105     # GH 25959 use pd.array instead of tolist
15:43:32     1106     # so extension arrays can be used
15:43:32     1107     return obj._constructor_expanddim(pd_array(mapped), index=obj.index)
15:43:32  
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/_libs/lib.pyx:2859, in pandas._libs.lib.map_infer()
15:43:32  
15:43:32  Cell In[24], line 10, in count_points(elem)
15:43:32        8 if hasattr(elem, "__len__"):  # then it is a MultiPolygon
15:43:32        9     return sum(_count(poly) for poly in elem)
15:43:32  ---> 10 return _count(elem)
15:43:32  
15:43:32  Cell In[24], line 4, in count_points.<locals>._count(poly)
15:43:32        3 def _count(poly):
15:43:32  ----> 4     return len(poly.exterior.coords) + sum(
15:43:32        5         len(hole.coords) for hole in poly.interiors
15:43:32        6     )
15:43:32  
15:43:32  AttributeError: 'MultiPolygon' object has no attribute 'exterior'
@huard
Copy link
Contributor

huard commented May 24, 2023

Proposed fix:

def count_points(elem):
    def _count(poly):
        return len(poly.exterior.coords) + sum(
            len(hole.coords) for hole in poly.interiors
        )
    if isinstance(elem, shapely.geometry.MultiPolygon):
        return sum(_count(poly) for poly in elem.geoms)
    return _count(elem)

tlvu added a commit that referenced this issue May 24, 2023
…tribute 'exterior' with upcoming Jupyter env

Fix proposed by @huard.

See #290

Fix for this error:
```
15:43:32  ____ pavics-sdi-fix_climex/docs/source/notebooks/regridding.ipynb::Cell 23 _____
15:43:32  Notebook cell execution failed
15:43:32  Cell 23: Cell execution caused an exception
15:43:32
15:43:32  Input:
15:43:32  # This is only to show the decrease in size
15:43:32  def count_points(elem):
15:43:32      def _count(poly):
15:43:32          return len(poly.exterior.coords) + sum(
15:43:32              len(hole.coords) for hole in poly.interiors
15:43:32          )
15:43:32
15:43:32      if hasattr(elem, "__len__"):  # then it is a MultiPolygon
15:43:32          return sum(_count(poly) for poly in elem)
15:43:32      return _count(elem)
15:43:32
15:43:32
15:43:32  # Count the total number of nodes in the shapes:
15:43:32  print(
15:43:32      "Total number of nodes in the raw shapes : ",
15:43:32      shapes.geometry.apply(count_points).sum(),
15:43:32  )
15:43:32
15:43:32  min_grid_size = float(
15:43:32      min(abs(ds_in.lat.diff("lat")).min(), abs(ds_in.lon.diff("lon")).min())
15:43:32  )
15:43:32  print(
15:43:32      f"Minimal grid size [��] of input ds: {min_grid_size:0.3f}, we will simplify to a tolerance of {min_grid_size / 100:0.5f}"
15:43:32  )
15:43:32
15:43:32  # Simplify geometries
15:43:32  shapes_simp = shapes.copy()
15:43:32  shapes_simp["geometry"] = shapes.simplify(min_grid_size / 100).buffer(0)
15:43:32
15:43:32  print(
15:43:32      "Total number of nodes in the simplified shapes : ",
15:43:32      shapes_simp.geometry.apply(count_points).sum(),
15:43:32  )
15:43:32  assert shapes_simp.buffer(0).is_valid.all()
15:43:32
15:43:32  Traceback:
15:43:32
15:43:32  ---------------------------------------------------------------------------
15:43:32  AttributeError                            Traceback (most recent call last)
15:43:32  Cell In[24], line 16
15:43:32       10     return _count(elem)
15:43:32       13 # Count the total number of nodes in the shapes:
15:43:32       14 print(
15:43:32       15     "Total number of nodes in the raw shapes : ",
15:43:32  ---> 16     shapes.geometry.apply(count_points).sum(),
15:43:32       17 )
15:43:32       19 min_grid_size = float(
15:43:32       20     min(abs(ds_in.lat.diff("lat")).min(), abs(ds_in.lon.diff("lon")).min())
15:43:32       21 )
15:43:32       22 print(
15:43:32       23     f"Minimal grid size [��] of input ds: {min_grid_size:0.3f}, we will simplify to a tolerance of {min_grid_size / 100:0.5f}"
15:43:32       24 )
15:43:32
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/geopandas/geoseries.py:645, in GeoSeries.apply(self, func, convert_dtype, args, **kwargs)
15:43:32      643 @doc(pd.Series)
15:43:32      644 def apply(self, func, convert_dtype=True, args=(), **kwargs):
15:43:32  --> 645     result = super().apply(func, convert_dtype=convert_dtype, args=args, **kwargs)
15:43:32      646     if isinstance(result, GeoSeries):
15:43:32      647         if self.crs is not None:
15:43:32
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/core/series.py:4357, in Series.apply(self, func, convert_dtype, args, **kwargs)
15:43:32     4247 def apply(
15:43:32     4248     self,
15:43:32     4249     func: AggFuncType,
15:43:32     (...)
15:43:32     4252     **kwargs,
15:43:32     4253 ) -> FrameOrSeriesUnion:
15:43:32     4254     """
15:43:32     4255     Invoke function on values of Series.
15:43:32     4256
15:43:32     (...)
15:43:32     4355     dtype: float64
15:43:32     4356     """
15:43:32  -> 4357     return SeriesApply(self, func, convert_dtype, args, kwargs).apply()
15:43:32
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/core/apply.py:1043, in SeriesApply.apply(self)
15:43:32     1039 if isinstance(self.f, str):
15:43:32     1040     # if we are a string, try to dispatch
15:43:32     1041     return self.apply_str()
15:43:32  -> 1043 return self.apply_standard()
15:43:32
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/core/apply.py:1098, in SeriesApply.apply_standard(self)
15:43:32     1092         values = obj.astype(object)._values
15:43:32     1093         # error: Argument 2 to "map_infer" has incompatible type
15:43:32     1094         # "Union[Callable[..., Any], str, List[Union[Callable[..., Any], str]],
15:43:32     1095         # Dict[Hashable, Union[Union[Callable[..., Any], str],
15:43:32     1096         # List[Union[Callable[..., Any], str]]]]]"; expected
15:43:32     1097         # "Callable[[Any], Any]"
15:43:32  -> 1098         mapped = lib.map_infer(
15:43:32     1099             values,
15:43:32     1100             f,  # type: ignore[arg-type]
15:43:32     1101             convert=self.convert_dtype,
15:43:32     1102         )
15:43:32     1104 if len(mapped) and isinstance(mapped[0], ABCSeries):
15:43:32     1105     # GH 25959 use pd.array instead of tolist
15:43:32     1106     # so extension arrays can be used
15:43:32     1107     return obj._constructor_expanddim(pd_array(mapped), index=obj.index)
15:43:32
15:43:32  File /opt/conda/envs/birdy/lib/python3.10/site-packages/pandas/_libs/lib.pyx:2859, in pandas._libs.lib.map_infer()
15:43:32
15:43:32  Cell In[24], line 10, in count_points(elem)
15:43:32        8 if hasattr(elem, "__len__"):  # then it is a MultiPolygon
15:43:32        9     return sum(_count(poly) for poly in elem)
15:43:32  ---> 10 return _count(elem)
15:43:32
15:43:32  Cell In[24], line 4, in count_points.<locals>._count(poly)
15:43:32        3 def _count(poly):
15:43:32  ----> 4     return len(poly.exterior.coords) + sum(
15:43:32        5         len(hole.coords) for hole in poly.interiors
15:43:32        6     )
15:43:32
15:43:32  AttributeError: 'MultiPolygon' object has no attribute 'exterior'
```
@huard huard closed this as completed in #281 Jun 1, 2023
huard added a commit that referenced this issue Jun 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants