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

cell_to_child_pos, child_pos_to_cell, cell_to_children_size #405

Merged
merged 18 commits into from
Sep 27, 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
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)
Loading