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

H3Shape subclasses accept __geo_interface__ arguments #322

Merged
merged 14 commits into from
Aug 13, 2023

Conversation

isaacbrodsky
Copy link
Collaborator

This builds on #301 to support passing objects with __geo_interface__, such as Shapely geometries, into H3.

I didn't add tests beyond what was already there and what was in the example notebook. Presumably we would want to add tests for this functionality specifically, based on the notebook.

I also assume we would remove poly/util.py as it appears to only exist for development purposes.

@codecov
Copy link

codecov bot commented Jun 18, 2023

Codecov Report

Patch coverage: 62.80% and project coverage change: -9.15% ⚠️

Comparison is base (ae9865c) 100.00% compared to head (f681b0b) 90.85%.
Report is 20 commits behind head on poly.

Additional details and impacted files
@@             Coverage Diff             @@
##              poly     #322      +/-   ##
===========================================
- Coverage   100.00%   90.85%   -9.15%     
===========================================
  Files           18       18              
  Lines          389      492     +103     
===========================================
+ Hits           389      447      +58     
- Misses           0       45      +45     
Files Changed Coverage Δ
src/h3/_cy/__init__.py 100.00% <ø> (ø)
src/h3/_polygon.py 59.57% <58.24%> (-40.43%) ⬇️
src/h3/api/_api_template.py 96.00% <74.07%> (-4.00%) ⬇️
src/h3/__init__.py 100.00% <100.00%> (ø)
src/h3/api/basic_int/_public_api.py 100.00% <100.00%> (ø)

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

src/h3/_polygon.py Outdated Show resolved Hide resolved
if len(holes) != 0:
raise ValueError("When copying from GeoJSON, holes cannot be specified")
self.outer = to_copy.polys[0].outer
self.holes = to_copy.polys[0].holes
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and above - just use self.holes = tuple() for clarity?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think I understand what line(s) are being referenced?

src/h3/_polygon.py Outdated Show resolved Hide resolved
ll2 = [
_swap_latlng(ll1)
for ll1 in ll2
]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to unclose the ring (i.e. delete the last coord if it duplicates the first)? Not really required for H3 code, but _polygon_to_LL2 and _LL2_to_polygon won't be fully symmetrical if the polygon input isn't closed. I guess this would be better enforced in the H3Poly constructor than here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that it would be nice to make these symmetric. I'd do that in the shape_to_geo function as described here: #322 (comment)

t = gj_dict['type']
coord = gj_dict['coordinates']
# functions below should be inverses of each other
def _LL3_to_geojson_dict(ll3):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit, but would it be more legible to use loops and loop instead of LL3 and LL2?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like this LL3/LL2 thing as it it seems clear what level of nesting is involved. That said it is not standard terminology.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there standard terminology we could switch to?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps loop/loops or ring/rings or something like that?

@@ -0,0 +1,8 @@
class MockGeoInterface:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice test helper 👍

@ajfriend
Copy link
Contributor

So h3.cells_to_shape() always returns an H3MultiPoly, even if the output can be represented as an H3Poly, which seems like reasonable behavior to me.

However, H3MultiPoly.__geo_interface__ will return a dict with 'type': 'Polygon' if possible. I wasn't sure when I was writing these functions what the best behavior would be. Do we think it is less surprising to the user if we always have:

H3Poly.__geo_interface__        ->   {'type': 'Polygon', ...}
H3MultiPoly.__geo_interface__   ->   {'type': 'MultiPolygon', ...}

@@ -220,8 +270,3 @@ def from_geo_interface(x):
ll3 = _geojson_dict_to_LL3(x)
mpoly = _LL3_to_mpoly(ll3)
return mpoly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we can remove check_geo_interface and from_geo_interface since they aren't used elsewhere.

@ajfriend
Copy link
Contributor

@isaacbrodsky, this looks great and ready to land into the poly branch. Thanks for working on it!

I have a few thoughts, but I'm happy to land this for now and follow up with changes on the poly branch.

I'm thinking about terminology and what would make the function behavior the most clear. It seems to me that we have four domains that we are translating between:

  1. H3 cells
  2. nested lists of lat/lng pairs
  3. H3Shape/H3Poly/H3MultiPoly
  4. "external" representations of polygons: GeoJSON dict or str, objects with __geo_interface__

Right now, the code uses "shape" to refer to both 3 and 4. We also do translations between the domains in three places: shape_to_cells() and the constructors for H3Poly and H3MultiPoly. I'd find it easier to understand the logic if we moved those translations to one place, and simplified the the constructors to only take in a single format. To me, that seems simpler to start, and we can always add back in the other optional input formats to the constructors later on.

For identifying the various domains, I'm considering the names (but very open to other suggestions):

  • "geo" to refer to objects in 3 or 4
  • "shape" to refer only to 3

If you have a nested list of lat/lng pairs, you just use the constructors for H3Poly or H3MultiPoly.

Those names lead us to the following functions/interface:

  • constructors for H3Poly or H3MultiPoly for list of lat/lng pairs
  • shape_to_cells accepts 3
  • cells_to_shape returns 3
  • geo_to_shape: accepts 3/4, returns 3
  • shape_to_geo: NOT IMPLEMENTED, since we already have __geo_interface__, but we could make a helper function
  • geo_to_cells: helper function, basically geo_to_shape -> shape_to_cells, accepts 3/4
  • cells_to_geo: NOT IMPLEMENTED, use cells_to_shape and __geo_interface__, but we could make a helper function

That being said, I imagine that most users will simply use geo_to_cells and cells_to_geo and won't need to know about these other functions and H3Poly/H3MultiPoly.

Again, @isaacbrodsky, I'm happy to land this PR as is, and follow up with these right after.

@isaacbrodsky isaacbrodsky merged commit 5c7614e into uber:poly Aug 13, 2023
@isaacbrodsky isaacbrodsky deleted the poly-2 branch August 13, 2023 16:23
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 this pull request may close these issues.

3 participants