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

upgrade to h3 v4.2.0 #432

Merged
merged 5 commits into from
Dec 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ avoid adding features or APIs which do not map onto the

## Unreleased

None.
- Update to v4.2.0.

## [4.1.2] - 2024-10-26

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = 'scikit_build_core.build'

[project]
name = 'h3'
version = '4.1.2'
version = '4.2.0'
description = "Uber's hierarchical hexagonal geospatial indexing system"
readme = 'readme.md'
license = {file = 'LICENSE'}
Expand Down
2 changes: 2 additions & 0 deletions src/h3/_cy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
cell_to_latlng,
polygon_to_cells,
polygons_to_cells,
polygon_to_cells_experimental,
polygons_to_cells_experimental,
cell_to_boundary,
directed_edge_to_boundary,
great_circle_distance,
Expand Down
3 changes: 3 additions & 0 deletions src/h3/_cy/h3lib.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ cdef extern from 'h3api.h':
H3Error maxPolygonToCellsSize(const GeoPolygon *geoPolygon, int res, uint32_t flags, uint64_t *count)
H3Error polygonToCells(const GeoPolygon *geoPolygon, int res, uint32_t flags, H3int *out)

H3Error maxPolygonToCellsSizeExperimental(const GeoPolygon *geoPolygon, int res, uint32_t flags, uint64_t *count)
H3Error polygonToCellsExperimental(const GeoPolygon *geoPolygon, int res, uint32_t flags, uint64_t sz, H3int *out)

# ctypedef struct GeoMultiPolygon:
# int numPolygons
# GeoPolygon *polygons
Expand Down
74 changes: 74 additions & 0 deletions src/h3/_cy/latlng.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,80 @@ def polygons_to_cells(polygons, int res):
return hmm.to_mv()


def polygon_to_cells_experimental(outer, int res, int flags, holes=None):
""" Get the set of cells whose center is contained in a polygon.

The polygon is defined similarity to the GeoJson standard, with an exterior
`outer` ring of lat/lng points, and a list of `holes`, each of which are also
rings of lat/lng points.

Each ring may be in clockwise or counter-clockwise order
(right-hand rule or not), and may or may not be a closed loop (where the last
element is equal to the first).
The GeoJSON spec requires the right-hand rule and a closed loop, but
this function relaxes those constraints.

Unlike the GeoJson standard, the elements of the lat/lng pairs of each
ring are in lat/lng order, instead of lng/lat order.

We'll handle translation to different formats in the Python code,
rather than the Cython code.

Parameters
----------
outer : list or tuple
A ring given by a sequence of lat/lng pairs.
res : int
The resolution of the output hexagons
flags : int
Polygon to cells flags, such as containment mode.
holes : list or tuple
A collection of rings, each given by a sequence of lat/lng pairs.
These describe any the "holes" in the polygon.
"""
cdef:
uint64_t n

check_res(res)

if not outer:
return H3MemoryManager(0).to_mv()

gp = GeoPolygon(outer, holes=holes)

check_for_error(
h3lib.maxPolygonToCellsSizeExperimental(&gp.gp, res, flags, &n)
)

hmm = H3MemoryManager(n)
check_for_error(
h3lib.polygonToCellsExperimental(&gp.gp, res, flags, n, hmm.ptr)
)
mv = hmm.to_mv()

return mv


def polygons_to_cells_experimental(polygons, int res, int flags):
mvs = [
polygon_to_cells_experimental(outer=poly.outer, res=res, holes=poly.holes, flags=flags)
for poly in polygons
]

n = sum(map(len, mvs))
hmm = H3MemoryManager(n)

# probably super inefficient, but it is working!
# tood: move this to C
k = 0
for mv in mvs:
for v in mv:
hmm.ptr[k] = v
k += 1

return hmm.to_mv()


def cell_to_boundary(H3int h):
"""Compose an array of geo-coordinates that outlines a hexagonal cell"""
cdef:
Expand Down
20 changes: 15 additions & 5 deletions src/h3/api/basic_int/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,7 @@
return _out_collection(hu)


def h3shape_to_cells(h3shape, res):
def h3shape_to_cells(h3shape, res, flags=0, experimental=False):
Copy link
Contributor

@ajfriend ajfriend Dec 22, 2024

Choose a reason for hiding this comment

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

I'm not sure if this should be the final form of the API. For example, we might not use integers for the flags, and we may drop the experimental option.

But it would be nice to get this functionality out so folks can start playing with it.

For the time being, could we leave h3shape_to_cells unchanged, and just create a h3shape_to_cells_experimental where we're free to change the API in the future?

"""
Return the collection of H3 cells at a given resolution whose center points
are contained within an ``LatLngPoly`` or ``LatLngMultiPoly``.
Expand All @@ -477,6 +477,10 @@
h3shape : ``H3Shape``
res : int
Resolution of the output cells
flags : int
Containment mode flags
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

flags seems like it needs an enum

experimental : bool
Whether to use experimental algorithm.

Returns
-------
Expand Down Expand Up @@ -506,10 +510,16 @@
# todo: not sure if i want this dispatch logic here. maybe in the objects?
if isinstance(h3shape, LatLngPoly):
poly = h3shape
mv = _cy.polygon_to_cells(poly.outer, res, holes=poly.holes)
if experimental:
mv = _cy.polygon_to_cells_experimental(poly.outer, res=res, holes=poly.holes, flags=flags)

Check failure on line 514 in src/h3/api/basic_int/__init__.py

View workflow job for this annotation

GitHub Actions / Coverage and Lint

Ruff (E501)

src/h3/api/basic_int/__init__.py:514:89: E501 Line too long (102 > 88)
else:
mv = _cy.polygon_to_cells(poly.outer, res=res, holes=poly.holes)
elif isinstance(h3shape, LatLngMultiPoly):
mpoly = h3shape
mv = _cy.polygons_to_cells(mpoly.polys, res)
if experimental:
mv = _cy.polygons_to_cells_experimental(mpoly.polys, res=res, flags=flags)
else:
mv = _cy.polygons_to_cells(mpoly.polys, res=res)
elif isinstance(h3shape, H3Shape):
raise ValueError('Unrecognized H3Shape: ' + str(h3shape))
else:
Expand All @@ -518,11 +528,11 @@
return _out_collection(mv)


def polygon_to_cells(h3shape, res):
def polygon_to_cells(h3shape, res, flags=0, experimental=False):
"""
Alias for ``h3shape_to_cells``.
"""
return h3shape_to_cells(h3shape, res)
return h3shape_to_cells(h3shape, res, flags=flags, experimental=experimental)


def cells_to_h3shape(cells, *, tight=True):
Expand Down
2 changes: 1 addition & 1 deletion src/h3lib
Submodule h3lib updated 186 files
40 changes: 40 additions & 0 deletions tests/polyfill/test_h3.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,46 @@
assert '89283080527ffff' in out
assert '89283095edbffff' in out

def test_polygon_to_cells_experimental():

Check failure on line 119 in tests/polyfill/test_h3.py

View workflow job for this annotation

GitHub Actions / Coverage and Lint

Ruff (E302)

tests/polyfill/test_h3.py:119:1: E302 Expected 2 blank lines, found 1
poly = h3.LatLngPoly(sf_7x7)
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells(poly, res=9, flags=0, experimental=True)

assert len(out) == 1253
assert '89283080527ffff' in out
assert '89283095edbffff' in out


def test_polygon_to_cells_experimental_full():
poly = h3.LatLngPoly(sf_7x7)
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells(poly, res=9, flags=1, experimental=True)

assert len(out) == 1175
assert '89283082a1bffff' in out
assert '89283080527ffff' not in out
assert '89283095edbffff' in out


def test_polygon_to_cells_experimental_overlapping():
poly = h3.LatLngPoly(sf_7x7)
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells(poly, res=9, flags=2, experimental=True)

assert len(out) == 1334
assert '89283080527ffff' in out
assert '89283095edbffff' in out


def test_polygon_to_cells_experimental_overlapping_bbox():
poly = h3.LatLngPoly(sf_7x7)
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells(poly, res=9, flags=3, experimental=True)

assert len(out) == 1416
assert '89283080527ffff' in out
assert '89283095edbffff' in out


def test_polyfill_with_hole():
poly = h3.LatLngPoly(sf_7x7, sf_hole1)
Expand Down
10 changes: 5 additions & 5 deletions tests/test_cells_and_edges.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,11 @@ def test_average_hexagon_area():

def test_average_hexagon_edge_length():
expected_in_km = {
0: 1107.712591000,
1: 418.676005500,
2: 158.244655800,
9: 0.174375668,
15: 0.000509713,
0: 1281.256011,
1: 483.0568391,
2: 182.5129565,
9: 0.200786148,
15: 0.000584169,
ajfriend marked this conversation as resolved.
Show resolved Hide resolved
}

out = {
Expand Down
Loading