h3-py icon indicating copy to clipboard operation
h3-py copied to clipboard

v4 Python Bindings: h3.Polygon has no "len()"

Open KcTheMapper opened this issue 2 years ago • 12 comments

h3.polygon_to_cells requires an h3 polygon input.

I get the following error when running h3.polygon_to_cells on an polygon, however: TypeError: object of type 'Polygon' has no len()

I see that there is a no length variable tied to the h3.Polygon object, but there is a value for lengh under Polygon.outer.length

This prevents me from doing any type of polyfill-ing (er, i mean polygon_to_cells-ifying)

Screen Shot 2022-09-13 at 2 54 19 PM

KcTheMapper avatar Sep 13 '22 19:09 KcTheMapper

Thanks for the report! It is definitely an experimental object, so I'm happy to hear any and all feedback you have about your experience using it!

What version of Python are you using? Is this a runtime error or a type-checking error?

ajfriend avatar Sep 21 '22 01:09 ajfriend

@KcTheMapper Are you trying to pass in a shapely polygon? I assume h3 only supports geojson-like coordinates

kylebarron avatar Sep 21 '22 07:09 kylebarron

Hey @ajfriend, I appreciate your attention to this!

I am using Python Version 3.9.6, h3 version 4 The error I am receiving is a Type-Error: "TypeError: object of type 'Polygon' has no len()"

@kylebarron I am trying to pass in an h3 polygon, which appears to be the only acceptable poly to pass into in v4.

KcTheMapper avatar Sep 21 '22 13:09 KcTheMapper

Apologies; I haven't followed the API changes in v4 and didn't know there was a new Polygon class

kylebarron avatar Sep 21 '22 15:09 kylebarron

@kylebarron no worries! All these changes have thrown me for quite the loop as well

KcTheMapper avatar Sep 21 '22 17:09 KcTheMapper

Can you share a code snippet that gives you this error? The following works for me:

import h3
poly = h3.Polygon(
    [(37.68, -122.54), (37.68, -122.34), (37.82, -122.34), (37.82, -122.54)],
    [(37.76, -122.51), (37.76, -122.44), (37.81, -122.51)],
)
h3.polygon_to_cells(poly, 6)

I can reproduce your error if I call len(poly), but I'm not sure if len() even makes sense for an h3.Polygon object. How would we define it? Number of holes? Total number of lat/lng points?

I'm curious what bit of your code might be trying to call len().

And it is true that Polygon is currently the only acceptable input for polygon_to_cells. I hope to add GeoJSON functionality to the next beta release to get feature parity with v3 (which will basically just convert the GeoJSON to an h3.Polygon or list of them and pass that off to polygon_to_cells).

Also, the h3.Polygon class was an experiment to clean up the API. It definitely isn't set in stone, and I would love any feedback or alternative proposals you might have.

ajfriend avatar Sep 21 '22 20:09 ajfriend

I'm encountering the same issue on h3==4.0.0b2 running in Python 3.10.8. Here's the code to reproduce

import geopandas as gpd
import h3

path = gpd.datasets.get_path('nybb')
df = gpd.read_file(path).to_crs(epsg=4326)

# finding largest polygon in multipolygon
polygon = max(df.loc[0, "geometry"].geoms, key=lambda a: a.area)
assert isinstance(polygon, shapely.geometry.polygon.Polygon)

h3_poly = h3.Polygon(polygon)
assert isinstance(h3_poly, h3.Polygon)

# raises "TypeError: object of type 'Polygon' has no len()"
h3.polygon_to_cells(h3_poly, 9) 

Stack Trace:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[58], line 10
      8 h3_poly = h3.Polygon(polygon)
      9 assert isinstance(h3_poly, h3.Polygon)
---> 10 h3.polygon_to_cells(h3_poly, 9) 

File [~/.pyenv/versions/3.10.8/envs/crash-data-extraction-3.10/lib/python3.10/site-packages/h3/api/_api_template.py:429](https://file+.vscode-resource.vscode-cdn.net/Users/sergey/Coding/business/three-sigma-etl/src/risk_score/notebooks/~/.pyenv/versions/3.10.8/envs/crash-data-extraction-3.10/lib/python3.10/site-packages/h3/api/_api_template.py:429), in _API_FUNCTIONS.polygon_to_cells(self, polygon, res)
    400 def polygon_to_cells(self, polygon, res):
    401     """
    402     Return the set of H3 cells at a given resolution whose center points
    403     are contained within a `h3.Polygon`
   (...)
    427      '86283095fffffff'}
    428     """
--> 429     mv = _cy.polygon_to_cells(polygon.outer, res, holes=polygon.holes)
    431     return self._out_unordered(mv)

File geo.pyx:160, in h3._cy.geo.polygon_to_cells()

File geo.pyx:108, in h3._cy.geo.GeoPolygon.__cinit__()

File geo.pyx:71, in h3._cy.geo.make_geoloop()

TypeError: object of type 'Polygon' has no len()

I was able to fix it by changing

# h3_poly = h3.Polygon(polygon)
h3_poly = h3.Polygon(polygon.exterior.coords)

My first thought is maybe this has something do with holes? When I print the working code I see

>>> h3_poly
<h3.Polygon |outer|=8877, |holes|=()>

Attempting to print the non working one returns an error (although it initializes with no issues).

>>> h3_poly
File [~/.pyenv/versions/3.10.8/envs/crash-data-extraction-3.10/lib/python3.10/site-packages/h3/_polygon.py:52](https://file+.vscode-resource.vscode-cdn.net/Users/sergey/Coding/business/three-sigma-etl/src/risk_score/notebooks/~/.pyenv/versions/3.10.8/envs/crash-data-extraction-3.10/lib/python3.10/site-packages/h3/_polygon.py:52), in Polygon.__repr__(self)
     50 def __repr__(self):
     51     s = '<h3.Polygon |outer|={}, |holes|={}>'.format(
---> 52         len(self.outer),
     53         tuple(map(len, self.holes)),
     54     )
     56     return s

TypeError: object of type 'Polygon' has no len()

I don't totally understand this behavior but it seems like a foot gun.

Filimoa avatar Feb 22 '23 23:02 Filimoa

@KcTheMapper Are you trying to pass in a shapely polygon? I assume h3 only supports geojson-like coordinates

it is quite the opposite. shapely IS "geojson"-compliant, but h3.Polygon is the opposite.

the whole difference is lon,lat vs lat,lon. the X,Y vs Y,X.

@ajfriend may i ask you to speed up that "geojson" support? i started with 4.0.0b2 last week and now i have to write a bunch of xy-yx conversion code. this is a bit inconvenient

spawn-guy avatar Feb 28 '23 12:02 spawn-guy

Ah, thanks for the info @spawn-guy! Indeed the old xy vs yx order seems to be at the root of my trouble.

I'm really not liking working with the h3.Polygon (with inners and outters) class. But i bet I could find some examples with which to better acquaint myself.

Thanks @Filimoa! That's getting me in a closer direction so it seems.

KcTheMapper avatar Mar 14 '23 22:03 KcTheMapper

@spawn-guy @KcTheMapper Thanks for the feedback on this! Apologies that I've let this languish in beta for so long. I'm hoping to get back into active development.

That being said, please do keep the feedback coming around the h3.Polygon class! It is very much an experiment with the interface, and we can change it now while we're in still beta. But we'll be stuck with whatever's in the final release. @KcTheMapper, what would feel more ergonomic to you?

ajfriend avatar Mar 23 '23 04:03 ajfriend

@ajfriend well, you asked for it 😅

Why not just implement the __geo_interface__?! In the way python describes it. I see this link in my Google https://gist.github.com/sgillies/2217756

I think, most of the current py geo tools support this. What do you think?

spawn-guy avatar Mar 23 '23 08:03 spawn-guy

@spawn-guy, I'm glad I did ask for it! This is great! 😄

I wasn't aware of the interface, but the fact that it seems pretty much standard makes things easier.

ajfriend avatar Mar 26 '23 02:03 ajfriend