1cimport h3lib
2from .h3lib cimport bool, int64_t, H3int
3from libc cimport stdlib
4
5from .util cimport (
6    check_cell,
7    check_res,
8    check_distance,
9    create_ptr,
10    create_mv,
11    empty_memory_view, # want to drop this import if possible
12)
13
14from .util import H3ValueError, H3ResolutionError
15
16# todo: add notes about Cython exception handling
17
18
19# bool is a python type, so we don't need the except clause
20cpdef bool is_cell(H3int h):
21    """Validates an H3 cell (hexagon or pentagon)
22
23    Returns
24    -------
25    boolean
26    """
27    return h3lib.h3IsValid(h) == 1
28
29cpdef bool is_pentagon(H3int h):
30    return h3lib.h3IsPentagon(h) == 1
31
32cpdef int get_base_cell(H3int h) except -1:
33    check_cell(h)
34
35    return h3lib.h3GetBaseCell(h)
36
37
38cpdef int resolution(H3int h) except -1:
39    """Returns the resolution of an H3 Index
40    0--15
41    """
42    check_cell(h)
43
44    return h3lib.h3GetResolution(h)
45
46
47cpdef int distance(H3int h1, H3int h2) except -1:
48    """ compute the hex-distance between two hexagons
49    """
50    check_cell(h1)
51    check_cell(h2)
52
53    d = h3lib.h3Distance(h1,h2)
54
55    if d < 0:
56        s = 'Cells are too far apart to compute distance: {} and {}'
57        s = s.format(hex(h1), hex(h2))
58        raise H3ValueError(s)
59
60    return d
61
62cpdef H3int[:] disk(H3int h, int k):
63    """ Return cells at grid distance `<= k` from `h`.
64    """
65    check_cell(h)
66    check_distance(k)
67
68    n = h3lib.maxKringSize(k)
69
70    ptr = create_ptr(n) # todo: return a "smart" pointer that knows its length?
71    h3lib.kRing(h, k, ptr)
72    mv = create_mv(ptr, n)
73
74    return mv
75
76
77cpdef H3int[:] _ring_fallback(H3int h, int k):
78    """
79    `ring` tries to call `h3lib.hexRing` first; if that fails, we call
80    this function, which relies on `h3lib.kRingDistances`.
81
82    Failures for `h3lib.hexRing` happen when the algortihm runs into a pentagon.
83    """
84    check_cell(h)
85    check_distance(k)
86
87    n = h3lib.maxKringSize(k)
88    # array of h3 cells
89    ptr = create_ptr(n)
90
91    # array of cell distances from `h`
92    dist_ptr = <int*> stdlib.calloc(n, sizeof(int))
93    if dist_ptr is NULL:
94        raise MemoryError()
95
96    h3lib.kRingDistances(h, k, ptr, dist_ptr)
97
98    distances = <int[:n]> dist_ptr
99    distances.callback_free_data = stdlib.free
100
101    for i,v in enumerate(distances):
102        if v != k:
103            ptr[i] = 0
104
105    mv = create_mv(ptr, n)
106
107    return mv
108
109cpdef H3int[:] ring(H3int h, int k):
110    """ Return cells at grid distance `== k` from `h`.
111    Collection is "hollow" for k >= 1.
112    """
113    check_cell(h)
114    check_distance(k)
115
116    n = 6*k if k > 0 else 1
117    ptr = create_ptr(n)
118
119    flag = h3lib.hexRing(h, k, ptr)
120
121    # if we drop into the failure state, we might be tempted to not create
122    # this mv, but creating the mv is exactly what guarantees that we'll free
123    # the memory. context manager would be better here, if we can figure out
124    # how to do that
125    mv = create_mv(ptr, n)
126
127    if flag != 0:
128        mv = _ring_fallback(h, k)
129
130    return mv
131
132
133cpdef H3int parent(H3int h, res=None) except 0:
134    check_cell(h)
135
136    if res is None:
137        res = resolution(h) - 1
138    if res > resolution(h):
139        msg = 'Invalid parent resolution {} for cell {}.'
140        msg = msg.format(res, hex(h))
141        raise H3ResolutionError(msg)
142
143    check_res(res)
144
145    return h3lib.h3ToParent(h, res)
146
147cpdef H3int[:] children(H3int h, res=None):
148    check_cell(h)
149
150    if res is None:
151        res = resolution(h) + 1
152    if res < resolution(h):
153        msg = 'Invalid child resolution {} for cell {}.'
154        msg = msg.format(res, hex(h))
155        raise H3ResolutionError(msg)
156
157    check_res(res)
158
159    n = h3lib.maxH3ToChildrenSize(h, res)
160
161    ptr = create_ptr(n)
162    h3lib.h3ToChildren(h, res, ptr)
163    mv = create_mv(ptr, n)
164
165    return mv
166
167cpdef H3int center_child(H3int h, res=None) except 0:
168    check_cell(h)
169
170    if res is None:
171        res = resolution(h) + 1
172    if res < resolution(h):
173        msg = 'Invalid child resolution {} for cell {}.'
174        msg = msg.format(res, hex(h))
175        raise H3ResolutionError(msg)
176
177    check_res(res)
178
179    return h3lib.h3ToCenterChild(h, res)
180
181
182
183cpdef H3int[:] compact(const H3int[:] hu):
184    # todo: the Clib can handle 0-len arrays because it **avoids**
185    # dereferencing the pointer, but Cython's syntax of
186    # `&hu[0]` **requires** a dereference. For Cython, checking for array
187    # length of zero and returning early seems like the easiest solution.
188    # note: open to better ideas!
189    if len(hu) == 0:
190        return empty_memory_view()
191
192    for h in hu: ## todo: should we have an array version? would that be faster?
193        check_cell(h)
194
195    ptr = create_ptr(len(hu))
196    flag = h3lib.compact(&hu[0], ptr, len(hu))
197    mv = create_mv(ptr, len(hu))
198
199    if flag != 0:
200        raise H3ValueError('Could not compact set of hexagons!')
201
202    return mv
203
204# todo: https://stackoverflow.com/questions/50684977/cython-exception-type-for-a-function-returning-a-typed-memoryview
205# apparently, memoryviews are python objects, so we don't need to do the except clause
206cpdef H3int[:] uncompact(const H3int[:] hc, int res):
207    # todo: the Clib can handle 0-len arrays because it **avoids**
208    # dereferencing the pointer, but Cython's syntax of
209    # `&hc[0]` **requires** a dereference. For Cython, checking for array
210    # length of zero and returning early seems like the easiest solution.
211    # note: open to better ideas!
212    if len(hc) == 0:
213        return empty_memory_view()
214
215    for h in hc:
216        check_cell(h)
217
218    N = h3lib.maxUncompactSize(&hc[0], len(hc), res)
219
220    ptr = create_ptr(N)
221    flag = h3lib.uncompact(
222        &hc[0], len(hc),
223           ptr, N,
224        res
225    )
226    mv = create_mv(ptr, N)
227
228    if flag != 0:
229        raise H3ValueError('Could not uncompact set of hexagons!')
230
231    return mv
232
233
234cpdef int64_t num_hexagons(int resolution) except -1:
235    check_res(resolution)
236
237    return h3lib.numHexagons(resolution)
238
239
240cpdef double mean_hex_area(int resolution, unit='km^2') except -1:
241    check_res(resolution)
242
243    area = h3lib.hexAreaKm2(resolution)
244
245    # todo: multiple units
246    convert = {
247        'km^2': 1.0,
248        'm^2': 1000*1000.0
249    }
250
251    try:
252        area *= convert[unit]
253    except:
254        raise H3ValueError('Unknown unit: {}'.format(unit))
255
256    return area
257
258
259cpdef double cell_area(H3int h, unit='km^2') except -1:
260    check_cell(h)
261
262    if unit == 'rads^2':
263        area = h3lib.cellAreaRads2(h)
264    elif unit == 'km^2':
265        area = h3lib.cellAreaKm2(h)
266    elif unit == 'm^2':
267        area = h3lib.cellAreaM2(h)
268    else:
269        raise H3ValueError('Unknown unit: {}'.format(unit))
270
271    return area
272
273
274cpdef H3int[:] line(H3int start, H3int end):
275    check_cell(start)
276    check_cell(end)
277
278    n = h3lib.h3LineSize(start, end)
279
280    if n < 0:
281        s = "Couldn't find line between cells {} and {}"
282        s = s.format(hex(start), hex(end))
283        raise H3ValueError(s)
284
285    ptr = create_ptr(n)
286    flag = h3lib.h3Line(start, end, ptr)
287    mv = create_mv(ptr, n)
288
289    if flag != 0:
290        s = "Couldn't find line between cells {} and {}"
291        s = s.format(hex(start), hex(end))
292        raise H3ValueError(s)
293
294    return mv
295
296cpdef bool is_res_class_iii(H3int h):
297    return h3lib.h3IsResClassIII(h) == 1
298
299
300cpdef H3int[:] get_pentagon_indexes(int res):
301    check_res(res)
302
303    n = h3lib.pentagonIndexCount()
304
305    ptr = create_ptr(n)
306    h3lib.getPentagonIndexes(res, ptr)
307    mv = create_mv(ptr, n)
308
309    return mv
310
311
312cpdef H3int[:] get_res0_indexes():
313    n = h3lib.res0IndexCount()
314
315    ptr = create_ptr(n)
316    h3lib.getRes0Indexes(ptr)
317    mv = create_mv(ptr, n)
318
319    return mv
320
321
322cpdef get_faces(H3int h):
323    check_cell(h)
324
325    n = h3lib.maxFaceCount(h)
326
327    cdef int* ptr = <int*> stdlib.calloc(n, sizeof(int))
328    if (n > 0) and (not ptr):
329        raise MemoryError()
330
331    h3lib.h3GetFaces(h, ptr)
332
333    faces = <int[:n]> ptr
334    faces = {f for f in faces if f >= 0}
335    stdlib.free(ptr)
336
337    return faces
338
339
340cpdef (int, int) experimental_h3_to_local_ij(H3int origin, H3int h) except *:
341    cdef:
342        int flag
343        h3lib.CoordIJ c
344
345    check_cell(origin)
346    check_cell(h)
347
348    flag = h3lib.experimentalH3ToLocalIj(origin, h, &c)
349
350    if flag != 0:
351        s = "Couldn't find local (i,j) between cells {} and {}."
352        s = s.format(hex(origin), hex(h))
353        raise H3ValueError(s)
354
355    return c.i, c.j
356
357
358cpdef H3int experimental_local_ij_to_h3(H3int origin, int i, int j) except 0:
359    cdef:
360        int flag
361        h3lib.CoordIJ c
362        H3int out
363
364    check_cell(origin)
365
366    c.i, c.j = i, j
367
368    flag = h3lib.experimentalLocalIjToH3(origin, &c, &out)
369
370    if flag != 0:
371        s = "Couldn't find cell at local ({},{}) from cell {}."
372        s = s.format(i, j, hex(origin))
373        raise H3ValueError(s)
374
375    return out
376