H3-Pandas icon indicating copy to clipboard operation
H3-Pandas copied to clipboard

ValueError using `polyfill` + `h3_to_geo_boundary`

Open CarlaFernandez opened this issue 1 year ago • 2 comments

Intro

I want to fill a polygon with hexagons of a given resolution and then get the boundaries for those hexagons. My code is the following:

import h3
import h3pandas

def generate_grid(region_bounds: gpd.GeoDataFrame, resolution=9) -> pd.DataFrame:
    grid = region_bounds.h3.polyfill(resolution, explode=True)
    grid = grid.drop('geometry', axis=1)
    grid = grid.rename(columns={'h3_polyfill': 'h3'})
    # grid = grid.set_index('h3')
    # Convert H3 indexes to their geometric boundaries
    grid['geometry'] = grid.h3.h3_to_geo_boundary()
    return grid

pp_cs_gdf = gpd.read_file('sample_error.geojson')
grid_9 = generate_grid(pp_cs_gdf, 9)

Error

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/util/decorator.py:27, in catch_invalid_h3_address.<locals>.safe_f(*args, **kwargs)
     26 try:
---> 27     return f(*args, **kwargs)
     28 except (TypeError, ValueError, H3CellError) as e:

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3/api/_api_template.py:294, in _API_FUNCTIONS.h3_to_geo_boundary(self, h, geo_json)
    277 """
    278 Return tuple of lat/lng pairs describing the cell boundary.
    279 
   (...)
    292 tuple of (float, float) tuples
    293 """
--> 294 return _cy.cell_boundary(self._in_scalar(h), geo_json)

TypeError: Argument 'h' has incorrect type (expected str, got int)

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
Cell In[120], line 13
     10     grid['geometry'] = grid.h3.h3_to_geo_boundary()
     11     return grid
---> 13 grid_9 = generate_grid(pp_cs_gdf, 9)

Cell In[120], line 10, in generate_grid(region_bounds, resolution)
      7 grid = grid.rename(columns={'h3_polyfill': 'h3'})
      8 # grid = grid.set_index('h3')
      9 # Convert H3 indexes to their geometric boundaries
---> 10 grid['geometry'] = grid.h3.h3_to_geo_boundary()
     11 return grid

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/h3pandas.py:160, in H3Accessor.h3_to_geo_boundary(self)
    139 def h3_to_geo_boundary(self) -> GeoDataFrame:
    140     """Add `geometry` with H3 hexagons to the DataFrame. Assumes H3 index.
    141 
    142     Returns
   (...)
    158     881e2659c3fffff    1  POLYGON ((14.99201 51.00565, 14.98973 51.00133...
    159     """
--> 160     return self._apply_index_assign(
    161         wrapped_partial(h3.h3_to_geo_boundary, geo_json=True),
    162         "geometry",
    163         lambda x: shapely.geometry.Polygon(x),
    164         lambda x: gpd.GeoDataFrame(x, crs="epsg:4326"),
    165     )

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/h3pandas.py:836, in H3Accessor._apply_index_assign(self, func, column_name, processor, finalizer)
    817 """Helper method. Applies `func` to index and assigns the result to `column`.
    818 
    819 Parameters
   (...)
    833 If using `finalizer`, can return anything the `finalizer` returns.
    834 """
    835 func = catch_invalid_h3_address(func)
--> 836 result = [processor(func(h3address)) for h3address in self._df.index]
    837 assign_args = {column_name: result}
    838 return finalizer(self._df.assign(**assign_args))

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/h3pandas.py:836, in <listcomp>(.0)
    817 """Helper method. Applies `func` to index and assigns the result to `column`.
    818 
    819 Parameters
   (...)
    833 If using `finalizer`, can return anything the `finalizer` returns.
    834 """
    835 func = catch_invalid_h3_address(func)
--> 836 result = [processor(func(h3address)) for h3address in self._df.index]
    837 assign_args = {column_name: result}
    838 return finalizer(self._df.assign(**assign_args))

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/util/decorator.py:32, in catch_invalid_h3_address.<locals>.safe_f(*args, **kwargs)
     30 message += f"\nCaller: {f.__name__}({_print_signature(*args, **kwargs)})"
     31 message += f"\nOriginal error: {repr(e)}"
---> 32 raise ValueError(message)

ValueError: H3 method raised an error. Is the H3 address correct?
Caller: h3_to_geo_boundary(17894)
Original error: TypeError("Argument 'h' has incorrect type (expected str, got int)")

Reproduce

  • h3==3.7.7
  • h3pandas===0.2.6
  • I attach a sample of the dataframe I'm using: sample_error.zip

CarlaFernandez avatar Apr 17 '24 10:04 CarlaFernandez

While the interface might not be as integrated as h3pandas, I did recently put up an example notebook that does exactly this with the new v4.0 beta of the plain h3-py library: https://uber.github.io/h3-py/polygon_tutorial.html

ajfriend avatar Apr 17 '24 16:04 ajfriend

So I managed to get it working for one polygon by changing my function like so:

def generate_grid(region_bounds: gpd.GeoDataFrame, resolution=9) -> pd.DataFrame:
    grid = region_bounds.h3.polyfill(resolution, explode=True)

    grid = grid.set_index('h3_polyfill')
    grid = grid.h3.h3_to_geo_boundary()

    return grid

If I input more than one polygon, now the error is different:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/util/decorator.py:27, in catch_invalid_h3_address.<locals>.safe_f(*args, **kwargs)
     26 try:
---> 27     return f(*args, **kwargs)
     28 except (TypeError, ValueError, H3CellError) as e:

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3/api/_api_template.py:294, in _API_FUNCTIONS.h3_to_geo_boundary(self, h, geo_json)
    277 """
    278 Return tuple of lat/lng pairs describing the cell boundary.
    279 
   (...)
    292 tuple of (float, float) tuples
    293 """
--> 294 return _cy.cell_boundary(self._in_scalar(h), geo_json)

TypeError: Argument 'h' has incorrect type (expected str, got float)

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
Cell In[24], line 14
     10     grid = grid.h3.h3_to_geo_boundary()
     12     return grid
---> 14 grid_9 = generate_grid(pp_cs_gdf[:2], 9)

Cell In[24], line 10, in generate_grid(region_bounds, resolution)
      8 grid = grid.set_index('h3_polyfill')
      9 display(grid)
---> 10 grid = grid.h3.h3_to_geo_boundary()
     12 return grid

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/h3pandas.py:160, in H3Accessor.h3_to_geo_boundary(self)
    139 def h3_to_geo_boundary(self) -> GeoDataFrame:
    140     """Add `geometry` with H3 hexagons to the DataFrame. Assumes H3 index.
    141 
    142     Returns
   (...)
    158     881e2659c3fffff    1  POLYGON ((14.99201 51.00565, 14.98973 51.00133...
    159     """
--> 160     return self._apply_index_assign(
    161         wrapped_partial(h3.h3_to_geo_boundary, geo_json=True),
    162         "geometry",
    163         lambda x: shapely.geometry.Polygon(x),
    164         lambda x: gpd.GeoDataFrame(x, crs="epsg:4326"),
    165     )

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/h3pandas.py:836, in H3Accessor._apply_index_assign(self, func, column_name, processor, finalizer)
    817 """Helper method. Applies `func` to index and assigns the result to `column`.
    818 
    819 Parameters
   (...)
    833 If using `finalizer`, can return anything the `finalizer` returns.
    834 """
    835 func = catch_invalid_h3_address(func)
--> 836 result = [processor(func(h3address)) for h3address in self._df.index]
    837 assign_args = {column_name: result}
    838 return finalizer(self._df.assign(**assign_args))

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/h3pandas.py:836, in <listcomp>(.0)
    817 """Helper method. Applies `func` to index and assigns the result to `column`.
    818 
    819 Parameters
   (...)
    833 If using `finalizer`, can return anything the `finalizer` returns.
    834 """
    835 func = catch_invalid_h3_address(func)
--> 836 result = [processor(func(h3address)) for h3address in self._df.index]
    837 assign_args = {column_name: result}
    838 return finalizer(self._df.assign(**assign_args))

File /anaconda/envs/azureml_py310_sdkv2/lib/python3.10/site-packages/h3pandas/util/decorator.py:32, in catch_invalid_h3_address.<locals>.safe_f(*args, **kwargs)
     30 message += f"\nCaller: {f.__name__}({_print_signature(*args, **kwargs)})"
     31 message += f"\nOriginal error: {repr(e)}"
---> 32 raise ValueError(message)

ValueError: H3 method raised an error. Is the H3 address correct?
Caller: h3_to_geo_boundary(nan)
Original error: TypeError("Argument 'h' has incorrect type (expected str, got float)")

When I display the grid, the h3_polyfill result contains NaN, and that seems to be the cause: image

Any idea why this NaN would appear?

CarlaFernandez avatar Apr 18 '24 07:04 CarlaFernandez