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