Skip to content

Commit f9c1a94

Browse files
authored
Bump h3lib to v4.4.1 and add new functions/errors (#470)
* bump h3lib to v4.4.0 * bump h3lib to v4.4.1 * tests pass with new version * add new error codes and some helper functions around error codes * jupyter-book<2 * is_valid_index * tests for is_valid_index and get_index_digit * linting * good set of tests * lint * thank you, coverage tests for tests! * int api test * function docstrings * maybe not * changelog. don't expose error code helper functions (yet) * ticks * don't even mention 'em * `list` rather than `tuple`, since users are likely to mutate result * remove justfile * failing tests * tests should be passing * more tests * swap docs order
1 parent 23bef19 commit f9c1a94

File tree

17 files changed

+442
-28
lines changed

17 files changed

+442
-28
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ h3c/
33
.cache/
44
h3/out/
55
.ruff_cache/
6+
_build/
67

78
# Generated C code from Cython test
89
tests/**/*.c

CHANGELOG.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,17 @@ avoid adding features or APIs which do not map onto the
1616

1717
## Unreleased
1818

19-
None.
19+
- Update `h3lib` to v4.4.1. (#470)
20+
- Add new error codes:
21+
- `H3IndexInvalidError`
22+
- `H3BaseCellDomainError`
23+
- `H3DigitDomainError`
24+
- `H3DeletedDigitError`
25+
- Add new functions:
26+
- `is_valid_index`
27+
- `get_index_digit`
28+
- `construct_cell`
29+
- `deconstruct_cell`
2030

2131
## [4.3.1] - 2025-08-10
2232

docs/api_quick.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and should be generally aligned with the
2525
is_res_class_III
2626
is_valid_directed_edge
2727
is_valid_vertex
28+
is_valid_index
2829
```
2930

3031
## Index representation
@@ -49,6 +50,9 @@ and should be generally aligned with the
4950
get_num_cells
5051
get_resolution
5152
get_base_cell_number
53+
get_index_digit
54+
construct_cell
55+
deconstruct_cell
5256
```
5357

5458
## Geographic coordinates

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = 'scikit_build_core.build'
44

55
[project]
66
name = 'h3'
7-
version = '4.3.1'
7+
version = '4.4.0'
88
description = "Uber's hierarchical hexagonal geospatial indexing system"
99
readme = 'readme.md'
1010
license = {file = 'LICENSE'}
@@ -50,7 +50,7 @@ numpy = ['numpy']
5050
test = ['pytest', 'pytest-cov', 'ruff', 'numpy']
5151
all = [
5252
'h3[test]',
53-
'jupyter-book',
53+
'jupyter-book<2',
5454
'sphinx>=7.3.3', # https://github.com/sphinx-doc/sphinx/issues/12290
5555
'jupyterlab',
5656
'jupyterlab-geojson',

src/h3/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,8 @@
2626
H3MemoryAllocError,
2727
H3MemoryBoundsError,
2828
H3OptionInvalidError,
29+
H3IndexInvalidError,
30+
H3BaseCellDomainError,
31+
H3DigitDomainError,
32+
H3DeletedDigitError,
2933
)

src/h3/_cy/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414
"""
1515

1616
from .cells import (
17+
is_valid_index,
1718
is_valid_cell,
1819
is_pentagon,
1920
get_base_cell_number,
2021
get_resolution,
22+
get_index_digit,
23+
construct_cell,
2124
cell_to_parent,
2225
grid_distance,
2326
grid_disk,
@@ -109,4 +112,11 @@
109112
H3MemoryAllocError,
110113
H3MemoryBoundsError,
111114
H3OptionInvalidError,
115+
H3IndexInvalidError,
116+
H3BaseCellDomainError,
117+
H3DigitDomainError,
118+
H3DeletedDigitError,
119+
120+
get_H3_ERROR_END,
121+
error_code_to_exception,
112122
)

src/h3/_cy/cells.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from .h3lib cimport bool, int64_t, H3int
22

3+
cpdef bool is_valid_index(H3int h)
34
cpdef bool is_valid_cell(H3int h)
45
cpdef bool is_pentagon(H3int h)
56
cpdef int get_base_cell_number(H3int h) except -1
67
cpdef int get_resolution(H3int h) except -1
8+
cpdef int get_index_digit(H3int h, int res) except -1
9+
cpdef H3int construct_cell(int baseCellNumber, const int[:] digits) except 0
710
cpdef int grid_distance(H3int h1, H3int h2) except -1
811
cpdef H3int[:] grid_disk(H3int h, int k)
912
cpdef H3int[:] grid_ring(H3int h, int k)

src/h3/_cy/cells.pyx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ from .h3lib cimport bool, int64_t, H3int, H3ErrorCodes
33

44
from .util cimport (
55
check_cell,
6-
check_res, # we don't use?
6+
check_index,
77
check_distance,
88
)
99

1010
from .error_system cimport (
11+
H3Error,
1112
check_for_error,
1213
check_for_error_msg,
1314
)
@@ -19,6 +20,14 @@ from .memory cimport (
1920

2021
# todo: add notes about Cython exception handling
2122

23+
cpdef bool is_valid_index(H3int h):
24+
"""Validates an H3 index (cell, vertex, or directed edge).
25+
26+
Returns
27+
-------
28+
boolean
29+
"""
30+
return h3lib.isValidIndex(h) == 1
2231

2332
# bool is a python type, so we don't need the except clause
2433
cpdef bool is_valid_cell(H3int h):
@@ -49,6 +58,33 @@ cpdef int get_resolution(H3int h) except -1:
4958

5059
return h3lib.getResolution(h)
5160

61+
cpdef int get_index_digit(H3int h, int res) except -1:
62+
cdef:
63+
int digit
64+
65+
check_index(h)
66+
67+
check_for_error(
68+
h3lib.getIndexDigit(h, res, &digit)
69+
)
70+
71+
return digit
72+
73+
cpdef H3int construct_cell(int base_cell_number, const int[:] digits) except 0:
74+
cdef:
75+
H3int out
76+
int res = len(digits)
77+
H3Error err
78+
79+
if res > 0:
80+
err = h3lib.constructCell(res, base_cell_number, &digits[0], &out)
81+
else:
82+
err = h3lib.constructCell(res, base_cell_number, NULL, &out)
83+
84+
check_for_error(err)
85+
86+
return out
87+
5288

5389
cpdef int grid_distance(H3int h1, H3int h2) except -1:
5490
""" Compute the grid distance between two cells

src/h3/_cy/error_system.pxd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
from .h3lib cimport H3Error
2+
3+
cpdef error_code_to_exception(H3Error err)
24
cdef check_for_error(H3Error err)
35
cdef check_for_error_msg(H3Error err, str msg)
6+
cpdef H3Error get_H3_ERROR_END()

src/h3/_cy/error_system.pyx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ Summarizing, all exceptions originating from the C library inherit from
6060
- H3MemoryAllocError
6161
- H3MemoryBoundsError
6262
- H3OptionInvalidError
63+
- H3IndexInvalidError
64+
- H3BaseCellDomainError
65+
- H3DigitDomainError
66+
- H3DeletedDigitError
6367
6468
6569
# TODO: add tests verifying that concrete exception classes have the right error codes associated with them
@@ -87,6 +91,11 @@ from .h3lib cimport (
8791
E_MEMORY_ALLOC,
8892
E_MEMORY_BOUNDS,
8993
E_OPTION_INVALID,
94+
E_INDEX_INVALID,
95+
E_BASE_CELL_DOMAIN,
96+
E_DIGIT_DOMAIN,
97+
E_DELETED_DIGIT,
98+
H3_ERROR_END # sentinel value
9099
)
91100

92101
@contextmanager
@@ -169,6 +178,10 @@ with _the_error(H3ValueError) as e:
169178
class H3NotNeighborsError(e): ...
170179
class H3ResMismatchError(e): ...
171180
class H3OptionInvalidError(e): ...
181+
class H3IndexInvalidError(e): ...
182+
class H3BaseCellDomainError(e): ...
183+
class H3DigitDomainError(e): ...
184+
class H3DeletedDigitError(e): ...
172185

173186

174187
"""
@@ -192,6 +205,10 @@ error_mapping = {
192205
E_MEMORY_ALLOC: H3MemoryAllocError,
193206
E_MEMORY_BOUNDS: H3MemoryBoundsError,
194207
E_OPTION_INVALID: H3OptionInvalidError,
208+
E_INDEX_INVALID: H3IndexInvalidError,
209+
E_BASE_CELL_DOMAIN: H3BaseCellDomainError,
210+
E_DIGIT_DOMAIN: H3DigitDomainError,
211+
E_DELETED_DIGIT: H3DeletedDigitError,
195212
}
196213

197214
# Go back and modify the class definitions so that each concrete exception
@@ -207,25 +224,37 @@ for code, ex in error_mapping.items():
207224
# TODO: Move the helpers to util?
208225
# TODO: Unclear how/where to expose these functions. cdef/cpdef?
209226

210-
cdef code_to_exception(H3Error err):
227+
cpdef error_code_to_exception(H3Error err):
228+
"""
229+
Return Python exception corresponding to integer error code
230+
given via the H3ErrorCodes enum in `h3api.h.in` in the C library.
231+
"""
211232
if err == E_SUCCESS:
212233
return None
213234
elif err in error_mapping:
214235
return error_mapping[err]
215236
else:
216-
raise UnknownH3ErrorCode(err)
237+
return UnknownH3ErrorCode(err)
217238

218239
cdef check_for_error(H3Error err):
219-
ex = code_to_exception(err)
240+
ex = error_code_to_exception(err)
220241
if ex:
221242
raise ex
222243

244+
cpdef H3Error get_H3_ERROR_END():
245+
"""
246+
Return integer H3_ERROR_END from the H3ErrorCodes enum
247+
in `h3api.h.in` in the C library, which is one greater than
248+
the last valid error code.
249+
"""
250+
return H3_ERROR_END
251+
223252
# todo: There's no easy way to do `*args` in `cdef` functions, but I'm also
224253
# not sure this even needs to be a Cython `cdef` function at all, or that
225254
# any of the other helper functions need to be in Cython.
226255
# todo: Revisit after we've played with this a bit.
227256
# todo: also: maybe the extra messages aren't that much more helpful...
228257
cdef check_for_error_msg(H3Error err, str msg):
229-
ex = code_to_exception(err)
258+
ex = error_code_to_exception(err)
230259
if ex:
231260
raise ex(msg)

0 commit comments

Comments
 (0)