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 all commits
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. (#432)

## [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
11 changes: 11 additions & 0 deletions src/h3/_h3shape.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
from abc import ABCMeta, abstractmethod
from enum import Enum


class ContainmentMode(int, Enum):
"""
Containment modes for use with ``polygon_to_cells_experimental``.
"""
containment_center = 0
containment_full = 1
containment_overlapping = 2
containment_overlapping_bbox = 3


class H3Shape(metaclass=ABCMeta):
Expand Down
82 changes: 80 additions & 2 deletions src/h3/api/basic_int/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from ... import _cy
from ..._h3shape import (
ContainmentMode,
H3Shape,
LatLngPoly,
LatLngMultiPoly,
Expand Down Expand Up @@ -506,10 +507,10 @@ def h3shape_to_cells(h3shape, res):
# 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)
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)
mv = _cy.polygons_to_cells(mpoly.polys, res=res)
elif isinstance(h3shape, H3Shape):
raise ValueError('Unrecognized H3Shape: ' + str(h3shape))
else:
Expand All @@ -525,6 +526,83 @@ def polygon_to_cells(h3shape, res):
return h3shape_to_cells(h3shape, res)


def h3shape_to_cells_experimental(h3shape, res, flags=0):
"""
Return the collection of H3 cells at a given resolution whose center points
are contained within an ``LatLngPoly`` or ``LatLngMultiPoly``.

Parameters
----------
h3shape : ``H3Shape``
res : int
Resolution of the output cells
flags : ``ContainmentMode``, int, or string
Containment mode flags

Returns
-------
list of H3Cell

Examples
--------

>>> poly = LatLngPoly(
... [(37.68, -122.54), (37.68, -122.34), (37.82, -122.34),
... (37.82, -122.54)],
... )
>>> h3.h3shape_to_cells_experimental(poly, 6, h3.ContainmentMode.containment_center)
['862830807ffffff',
'862830827ffffff',
'86283082fffffff',
'862830877ffffff',
'862830947ffffff',
'862830957ffffff',
'86283095fffffff']

Notes
-----
There is currently no guaranteed order of the output cells.
"""

if isinstance(flags, str):
try:
flags = ContainmentMode[flags]
except KeyError as e:
raise ValueError('Unrecognized flags: ' + flags) from e
if isinstance(flags, ContainmentMode):
flags = int(flags)
if not isinstance(flags, int):
raise ValueError(
'Flags should be ContainmentMode, str, or int, but got: ' + str(type(flags))
)

# 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_experimental(
poly.outer,
res=res,
holes=poly.holes,
flags=flags
)
elif isinstance(h3shape, LatLngMultiPoly):
mpoly = h3shape
mv = _cy.polygons_to_cells_experimental(mpoly.polys, res=res, flags=flags)
elif isinstance(h3shape, H3Shape):
raise ValueError('Unrecognized H3Shape: ' + str(h3shape))
else:
raise ValueError('Unrecognized type: ' + str(type(h3shape)))

return _out_collection(mv)


def polygon_to_cells_experimental(h3shape, res, flags=0):
"""
Alias for ``h3shape_to_cells_experimental``.
"""
return h3shape_to_cells_experimental(h3shape, res, flags=flags)


def cells_to_h3shape(cells, *, tight=True):
"""
Return an ``H3Shape`` describing the area covered by a collection of H3 cells.
Expand Down
2 changes: 1 addition & 1 deletion src/h3lib
Submodule h3lib updated 186 files
62 changes: 62 additions & 0 deletions tests/polyfill/test_h3.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,68 @@ def test_polygon_to_cells():
assert '89283095edbffff' in out


def test_polygon_to_cells_experimental():
poly = h3.LatLngPoly(sf_7x7)
for flags in [0, 'containment_center', h3.ContainmentMode.containment_center]:
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags)

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


def test_polygon_to_cells_experimental_full():
poly = h3.LatLngPoly(sf_7x7)
for flags in [1, 'containment_full', h3.ContainmentMode.containment_full]:
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags)

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)
for flags in [
2,
'containment_overlapping',
h3.ContainmentMode.containment_overlapping
]:
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags)

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)
for flags in [
3,
'containment_overlapping_bbox',
h3.ContainmentMode.containment_overlapping_bbox
]:
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
out = h3.polygon_to_cells_experimental(poly, res=9, flags=flags)

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


def test_polygon_to_cells_experimental_invalid_mode():
poly = h3.LatLngPoly(sf_7x7)
for flags in [1.0, 'containment_overlapping_bbox_abc', None]:
with pytest.raises(ValueError):
print(flags)
# Note that `polygon_to_cells` is an alias for `h3shape_to_cells`
h3.polygon_to_cells_experimental(poly, res=9, flags=flags)


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