Skip to content

Commit

Permalink
cell_to_child_pos, child_pos_to_cell, cell_to_children_size (#405)
Browse files Browse the repository at this point in the history
* cell_to_child_pos and child_pos_to_cell

* start exposing cell_to_children_size

* checking resolutions

* cell_to_children_size python function

* adding tests from #404

* fiddling

* fix test_cell_to_vertex; helps coverage by removing untouched branch

* more tests

* lint

* exhaustive test for a few resolution pairs

* lint

* doc strings

* error messages

* stricter test

* playing with argument order. example code in tests looks nicer

* use parent_res instead of res

* Use `res_*` instead of `*_res`

* fix up changelog
  • Loading branch information
ajfriend authored Sep 27, 2024
1 parent b168018 commit 259f31b
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 19 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@ avoid adding features or APIs which do not map onto the

## Unreleased

None.
- Add `cell_to_child_pos`, `child_pos_to_cell`, `cell_to_children_size` (#405)

## [4.1.0b1] - 2024-09-26

- Bump h3lib to v4.1.0 (#402)
- Add `polygon_to_cells` alias (#399)

## [4.0.0b7] - 2024-09-04

- Use `pyproject.toml` and `scikit-build-core` (#378)

## [4.0.0b6] - 2024-09-03

- Added bindings for `cellToVertex`, `cellToVertexes`, `vertexToLatLng`, and `isValidVertex` (#323)
- Added bindings for `cellToVertex`, `cellToVertexes`, `vertexToLatLng`, and `isValidVertex` (#388)

## [4.0.0b5] - 2024-05-19

Expand Down
3 changes: 3 additions & 0 deletions docs/api_quick.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ Functions relating H3 objects to geographic (lat/lng) coordinates.
cell_to_parent
cell_to_children
cell_to_center_child
cell_to_children_size
cell_to_child_pos
child_pos_to_cell
compact_cells
uncompact_cells
```
Expand Down
3 changes: 3 additions & 0 deletions src/h3/_cy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@
grid_distance,
grid_disk,
grid_ring,
cell_to_children_size,
cell_to_children,
cell_to_child_pos,
child_pos_to_cell,
compact_cells,
uncompact_cells,
get_num_cells,
Expand Down
3 changes: 3 additions & 0 deletions src/h3/_cy/cells.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ cpdef int grid_distance(H3int h1, H3int h2) except -1
cpdef H3int[:] grid_disk(H3int h, int k)
cpdef H3int[:] grid_ring(H3int h, int k)
cpdef H3int cell_to_parent(H3int h, res=*) except 0
cpdef int64_t cell_to_children_size(H3int h, res=*) except -1
cpdef H3int[:] cell_to_children(H3int h, res=*)
cpdef H3int cell_to_center_child(H3int h, res=*) except 0
cpdef int64_t cell_to_child_pos(H3int child, int parent_res) except -1
cpdef H3int child_pos_to_cell(H3int parent, int child_res, int64_t child_pos) except 0
cpdef H3int[:] compact_cells(const H3int[:] hu)
cpdef H3int[:] uncompact_cells(const H3int[:] hc, int res)
cpdef int64_t get_num_cells(int resolution) except -1
Expand Down
48 changes: 42 additions & 6 deletions src/h3/_cy/cells.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ from .h3lib cimport bool, int64_t, H3int, H3ErrorCodes

from .util cimport (
check_cell,
check_res,
check_res, # we don't use?
check_distance,
)

Expand Down Expand Up @@ -139,12 +139,12 @@ cpdef H3int[:] grid_ring(H3int h, int k):

return mv


cpdef H3int cell_to_parent(H3int h, res=None) except 0:
cdef:
H3int parent

check_cell(h) # todo: do we want to check for validity here? or leave correctness to the user?

check_cell(h)
if res is None:
res = get_resolution(h) - 1

Expand All @@ -157,12 +157,11 @@ cpdef H3int cell_to_parent(H3int h, res=None) except 0:
return parent


cpdef H3int[:] cell_to_children(H3int h, res=None):
cpdef int64_t cell_to_children_size(H3int h, res=None) except -1:
cdef:
int64_t n

check_cell(h)

if res is None:
res = get_resolution(h) + 1

Expand All @@ -172,6 +171,16 @@ cpdef H3int[:] cell_to_children(H3int h, res=None):
msg = msg.format(res, hex(h))
check_for_error_msg(err, msg)

return n


cpdef H3int[:] cell_to_children(H3int h, res=None):
check_cell(h)
if res is None:
res = get_resolution(h) + 1

n = cell_to_children_size(h, res)

hmm = H3MemoryManager(n)
check_for_error(
h3lib.cellToChildren(h, res, hmm.ptr)
Expand All @@ -181,12 +190,12 @@ cpdef H3int[:] cell_to_children(H3int h, res=None):
return mv



cpdef H3int cell_to_center_child(H3int h, res=None) except 0:
cdef:
H3int child

check_cell(h)

if res is None:
res = get_resolution(h) + 1

Expand All @@ -199,6 +208,33 @@ cpdef H3int cell_to_center_child(H3int h, res=None) except 0:
return child


cpdef int64_t cell_to_child_pos(H3int child, int parent_res) except -1:
cdef:
int64_t child_pos

check_cell(child)
err = h3lib.cellToChildPos(child, parent_res, &child_pos)
if err:
msg = "Couldn't find child pos of cell {} at res {}."
msg = msg.format(hex(child), parent_res)
check_for_error_msg(err, msg)

return child_pos


cpdef H3int child_pos_to_cell(H3int parent, int child_res, int64_t child_pos) except 0:
cdef:
H3int child

check_cell(parent)
err = h3lib.childPosToCell(child_pos, parent, child_res, &child)
if err:
msg = "Couldn't find child with pos {} at res {} from parent {}."
msg = msg.format(child_pos, child_res, hex(parent))
check_for_error_msg(err, msg)

return child


cpdef H3int[:] compact_cells(const H3int[:] hu):
# todo: fix this with my own Cython object "wrapper" class?
Expand Down
2 changes: 2 additions & 0 deletions src/h3/_cy/h3lib.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ cdef extern from 'h3api.h':

H3Error cellToParent( H3int h, int parentRes, H3int *parent) nogil
H3Error cellToCenterChild(H3int h, int childRes, H3int *child) nogil
H3Error cellToChildPos(H3int child, int parentRes, int64_t *out) nogil
H3Error childPosToCell(int64_t childPos, H3int parent, int childRes, H3int *child) nogil

H3Error cellToChildrenSize(H3int h, int childRes, int64_t *num) nogil # num/out/N?
H3Error cellToChildren( H3int h, int childRes, H3int *children) nogil
Expand Down
84 changes: 78 additions & 6 deletions src/h3/api/basic_int/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ def cell_to_latlng(h):
lng : float
Longitude
"""
return _cy.cell_to_latlng(_in_scalar(h))
h = _in_scalar(h)
return _cy.cell_to_latlng(h)


def get_resolution(h):
Expand All @@ -201,7 +202,8 @@ def get_resolution(h):
int
"""
# todo: could also work for edges
return _cy.get_resolution(_in_scalar(h))
h = _in_scalar(h)
return _cy.get_resolution(h)


def cell_to_parent(h, res=None):
Expand Down Expand Up @@ -266,7 +268,8 @@ def cell_to_boundary(h):
-------
tuple of (lat, lng) tuples
"""
return _cy.cell_to_boundary(_in_scalar(h))
h = _in_scalar(h)
return _cy.cell_to_boundary(h)


def grid_disk(h, k=1):
Expand All @@ -288,7 +291,8 @@ def grid_disk(h, k=1):
-----
There is currently no guaranteed order of the output cells.
"""
mv = _cy.grid_disk(_in_scalar(h), k)
h = _in_scalar(h)
mv = _cy.grid_disk(h, k)

return _out_collection(mv)

Expand All @@ -312,11 +316,32 @@ def grid_ring(h, k=1):
-----
There is currently no guaranteed order of the output cells.
"""
mv = _cy.grid_ring(_in_scalar(h), k)
h = _in_scalar(h)
mv = _cy.grid_ring(h, k)

return _out_collection(mv)


def cell_to_children_size(h, res=None):
"""
Number of children at resolution ``res`` of given cell.
Parameters
----------
h : H3Cell
res : int or None, optional
The resolution for the children.
If ``None``, then ``res = resolution(h) + 1``
Returns
-------
int
Count of children
"""
h = _in_scalar(h)
return _cy.cell_to_children_size(h, res)


def cell_to_children(h, res=None):
"""
Children of a cell as an unordered collection.
Expand All @@ -336,11 +361,58 @@ def cell_to_children(h, res=None):
-----
There is currently no guaranteed order of the output cells.
"""
mv = _cy.cell_to_children(_in_scalar(h), res)
h = _in_scalar(h)
mv = _cy.cell_to_children(h, res)

return _out_collection(mv)


def cell_to_child_pos(child, res_parent):
"""
Child position index of given cell, with respect to its parent at ``res_parent``.
The reverse operation can be done with ``child_pos_to_cell``.
Parameters
----------
child : H3Cell
res_parent : int
Returns
-------
int
Integer index of the child with respect to parent cell.
"""
child = _in_scalar(child)
return _cy.cell_to_child_pos(child, res_parent)


def child_pos_to_cell(parent, res_child, child_pos):
"""
Get child H3 cell from a parent cell, child resolution, and child position index.
The reverse operation can be done with ``cell_to_child_pos``.
Parameters
----------
parent : H3Cell
res_child : int
Child cell resolution
child_pos : int
Integer position of child cell, releative to parent.
Returns
-------
H3Cell
"""
parent = _in_scalar(parent)
child = _cy.child_pos_to_cell(parent, res_child, child_pos)
child = _out_scalar(child)

return child


# todo: nogil for expensive C operation?
def compact_cells(cells):
"""
Expand Down
73 changes: 68 additions & 5 deletions tests/test_h3.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,9 @@ def test_cell_to_vertex():
assert h3.cell_to_vertex('814c3ffffffffff', 2) == '2214c3ffffffffff'
assert h3.cell_to_vertex('814c3ffffffffff', 3) == '2314c3ffffffffff'
assert h3.cell_to_vertex('814c3ffffffffff', 4) == '2414c3ffffffffff'
try:

with pytest.raises(h3.H3DomainError):
h3.cell_to_vertex('814c3ffffffffff', 5)
except h3._cy.error_system.H3DomainError:
pass
else:
assert False

# hexagon
assert h3.cell_to_vertex('814d7ffffffffff', 0) == '2014d7ffffffffff'
Expand Down Expand Up @@ -446,3 +443,69 @@ def test_is_valid_vertex():
assert h3.is_valid_vertex('2114c3ffffffffff')
assert not h3.is_valid_vertex(2455495337847029759)
assert not h3.is_valid_vertex('foobar')


def test_child_pos():
h = '88283080ddfffff'

assert h3.cell_to_child_pos(h, 8) == 0
assert h3.cell_to_child_pos(h, 7) == 6
assert h3.cell_to_child_pos(h, 6) == 41

with pytest.raises(h3.H3BaseException):
h3.cell_to_child_pos(h, 9)

with pytest.raises(h3.H3BaseException):
h3.child_pos_to_cell(h, 9, -1)

with pytest.raises(h3.H3BaseException):
h3.child_pos_to_cell(h, 9, 10000)


def test_child_pos2():
h = '88283080ddfffff'
assert h == h3.child_pos_to_cell(h, 8, 0)
assert h == h3.child_pos_to_cell(h3.cell_to_parent(h, 7), 8, 6)
assert h == h3.child_pos_to_cell(h3.cell_to_parent(h, 6), 8, 41)


def test_cell_to_children_size():
h = '8053fffffffffff' # hexagon
for r in range(16):
assert h3.cell_to_children_size(h, r) == 7**r


def test_cell_to_children_size2():
cells = h3.get_res0_cells()

for r in range(16):
total_cells = 120 * (7**r) + 2

assert total_cells == sum(
h3.cell_to_children_size(h, r)
for h in cells
)


def test_child_pos3():
def cells_at_res(res):
cells = h3.get_res0_cells()
for parent in cells:
yield from h3.cell_to_children(parent, res)

def roundtrip(child, res_parent):
res_child = h3.get_resolution(child)
parent = h3.cell_to_parent(child, res_parent)
pos = h3.cell_to_child_pos(child, res_parent)
return h3.child_pos_to_cell(parent, res_child, pos)

res_pairs = [
(res_parent, res_child)
for res_parent in [0, 1]
for res_child in [0, 1, 2, 3]
if res_parent <= res_child
]

for res_parent, res_child in res_pairs:
for child in cells_at_res(res_child):
assert child == roundtrip(child, res_parent)

0 comments on commit 259f31b

Please sign in to comment.