Files
StarPilot/frogpilot/third_party/h3/_cy/cells.pyx
T
2026-02-13 00:21:29 -07:00

420 lines
9.3 KiB
Cython

cimport h3lib
from .h3lib cimport bool, int64_t, H3int, H3ErrorCodes
from .util cimport (
check_cell,
check_res, # we don't use?
check_distance,
)
from .error_system cimport (
check_for_error,
check_for_error_msg,
)
from .memory cimport (
H3MemoryManager,
int_mv,
)
# todo: add notes about Cython exception handling
# bool is a python type, so we don't need the except clause
cpdef bool is_valid_cell(H3int h):
"""Validates an H3 cell (hexagon or pentagon)
Returns
-------
boolean
"""
return h3lib.isValidCell(h) == 1
cpdef bool is_pentagon(H3int h):
return h3lib.isPentagon(h) == 1
cpdef int get_base_cell_number(H3int h) except -1:
check_cell(h)
return h3lib.getBaseCellNumber(h)
cpdef int get_resolution(H3int h) except -1:
"""Returns the resolution of an H3 Index
0--15
"""
check_cell(h)
return h3lib.getResolution(h)
cpdef int grid_distance(H3int h1, H3int h2) except -1:
""" Compute the grid distance between two cells
"""
cdef:
int64_t distance
check_cell(h1)
check_cell(h2)
check_for_error(
h3lib.gridDistance(h1, h2, &distance)
)
return distance
cpdef H3int[:] grid_disk(H3int h, int k):
""" Return cells at grid distance `<= k` from `h`.
"""
cdef:
int64_t n
check_cell(h)
check_distance(k)
check_for_error(
h3lib.maxGridDiskSize(k, &n)
)
hmm = H3MemoryManager(n)
check_for_error(
h3lib.gridDisk(h, k, hmm.ptr)
)
mv = hmm.to_mv()
return mv
cpdef H3int[:] grid_ring(H3int h, int k):
""" Return cells at grid distance `== k` from `h`.
Collection is "hollow" for k >= 1.
"""
check_cell(h)
check_distance(k)
n = 6*k if k > 0 else 1
hmm = H3MemoryManager(n)
check_for_error(
h3lib.gridRing(h, k, hmm.ptr)
)
mv = hmm.to_mv()
return mv
cpdef H3int cell_to_parent(H3int h, res=None) except 0:
cdef:
H3int parent
check_cell(h)
if res is None:
res = get_resolution(h) - 1
err = h3lib.cellToParent(h, res, &parent)
if err:
msg = 'Invalid parent resolution {} for cell {}.'
msg = msg.format(res, hex(h))
check_for_error_msg(err, msg)
return parent
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
err = h3lib.cellToChildrenSize(h, res, &n)
if err:
msg = 'Invalid child resolution {} for cell {}.'
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)
)
mv = hmm.to_mv()
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
err = h3lib.cellToCenterChild(h, res, &child)
if err:
msg = 'Invalid child resolution {} for cell {}.'
msg = msg.format(res, hex(h))
check_for_error_msg(err, msg)
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?
# everything has a .ptr interface?
# todo: the Clib can handle 0-len arrays because it **avoids**
# dereferencing the pointer, but Cython's syntax of
# `&hu[0]` **requires** a dereference. For Cython, checking for array
# length of zero and returning early seems like the easiest solution.
# note: open to better ideas!
if len(hu) == 0:
return H3MemoryManager(0).to_mv()
for h in hu: ## todo: should we have an array version? would that be faster?
check_cell(h)
cdef size_t n = len(hu)
hmm = H3MemoryManager(n)
check_for_error(
h3lib.compactCells(&hu[0], hmm.ptr, n)
)
mv = hmm.to_mv()
return mv
# todo: https://stackoverflow.com/questions/50684977/cython-exception-type-for-a-function-returning-a-typed-memoryview
# apparently, memoryviews are python objects, so we don't need to do the except clause
cpdef H3int[:] uncompact_cells(const H3int[:] hc, int res):
# todo: the Clib can handle 0-len arrays because it **avoids**
# dereferencing the pointer, but Cython's syntax of
# `&hc[0]` **requires** a dereference. For Cython, checking for array
# length of zero and returning early seems like the easiest solution.
# note: open to better ideas!
cdef:
int64_t n
if len(hc) == 0:
return H3MemoryManager(0).to_mv()
for h in hc:
check_cell(h)
check_for_error(
h3lib.uncompactCellsSize(&hc[0], len(hc), res, &n)
)
hmm = H3MemoryManager(n)
check_for_error(
h3lib.uncompactCells(
&hc[0], # todo: symmetry here with the wrapper object might be nice. hc.ptr / hc.n
len(hc),
hmm.ptr,
hmm.n,
res
)
)
mv = hmm.to_mv()
return mv
cpdef int64_t get_num_cells(int resolution) except -1:
cdef:
int64_t num_cells
check_for_error(
h3lib.getNumCells(resolution, &num_cells)
)
return num_cells
cpdef double average_hexagon_area(int resolution, unit='km^2') except -1:
cdef:
double area
check_for_error(
h3lib.getHexagonAreaAvgKm2(resolution, &area)
)
# todo: multiple units
convert = {
'km^2': 1.0,
'm^2': 1000*1000.0
}
try:
area *= convert[unit]
except:
raise ValueError('Unknown unit: {}'.format(unit))
return area
cpdef double cell_area(H3int h, unit='km^2') except -1:
cdef:
double area
if unit == 'rads^2':
err = h3lib.cellAreaRads2(h, &area)
elif unit == 'km^2':
err = h3lib.cellAreaKm2(h, &area)
elif unit == 'm^2':
err = h3lib.cellAreaM2(h, &area)
else:
raise ValueError('Unknown unit: {}'.format(unit))
check_for_error(err)
return area
cdef _could_not_find_line(err, start, end):
msg = "Couldn't find line between cells {} and {}"
msg = msg.format(hex(start), hex(end))
check_for_error_msg(err, msg)
cpdef H3int[:] grid_path_cells(H3int start, H3int end):
cdef:
int64_t n
# todo: can we segfault here with invalid inputs?
# Can we trust the c library to validate the start/end cells?
# probably applies to all size/work pairs of functions...
err = h3lib.gridPathCellsSize(start, end, &n)
_could_not_find_line(err, start, end)
hmm = H3MemoryManager(n)
err = h3lib.gridPathCells(start, end, hmm.ptr)
_could_not_find_line(err, start, end)
# todo: probably here too?
mv = hmm.to_mv()
return mv
cpdef bool is_res_class_iii(H3int h):
return h3lib.isResClassIII(h) == 1
cpdef H3int[:] get_pentagons(int res):
n = h3lib.pentagonCount()
hmm = H3MemoryManager(n)
check_for_error(
h3lib.getPentagons(res, hmm.ptr)
)
mv = hmm.to_mv()
return mv
cpdef H3int[:] get_res0_cells():
n = h3lib.res0CellCount()
hmm = H3MemoryManager(n)
check_for_error(
h3lib.getRes0Cells(hmm.ptr)
)
mv = hmm.to_mv()
return mv
# oh, this is returning a set??
# todo: convert to int[:]?
cpdef get_icosahedron_faces(H3int h):
cdef:
int n
int[:] faces ## todo: weird, this needs to be specified to avoid errors. cython bug?
check_for_error(
h3lib.maxFaceCount(h, &n)
)
faces = int_mv(n)
check_for_error(
h3lib.getIcosahedronFaces(h, &faces[0])
)
# todo: wait? do faces start from 0 or 1?
# we could do this check/processing in the int_mv object
out = [f for f in faces if f >= 0]
return out
cpdef (int, int) cell_to_local_ij(H3int origin, H3int h) except *:
cdef:
h3lib.CoordIJ c
err = h3lib.cellToLocalIj(origin, h, 0, &c)
if err:
msg = "Couldn't find local (i,j) between cells {} and {}."
msg = msg.format(hex(origin), hex(h))
check_for_error_msg(err, msg)
return c.i, c.j
cpdef H3int local_ij_to_cell(H3int origin, int i, int j) except 0:
cdef:
h3lib.CoordIJ c
H3int out
c.i, c.j = i, j
err = h3lib.localIjToCell(origin, &c, 0, &out)
if err:
msg = "Couldn't find cell at local ({},{}) from cell {}."
msg = msg.format(i, j, hex(origin))
check_for_error_msg(err, msg)
return out