1 | /* |
2 | * Copyright 2016-2018 Uber Technologies, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | /** @file h3Index.c |
17 | * @brief H3Index utility functions |
18 | * (see h3api.h for the main library entry functions) |
19 | */ |
20 | #include "h3Index.h" |
21 | #include <faceijk.h> |
22 | #include <inttypes.h> |
23 | #include <math.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include "baseCells.h" |
27 | #include "faceijk.h" |
28 | #include "mathExtensions.h" |
29 | #include "stackAlloc.h" |
30 | |
31 | /** |
32 | * Returns the H3 resolution of an H3 index. |
33 | * @param h The H3 index. |
34 | * @return The resolution of the H3 index argument. |
35 | */ |
36 | int H3_EXPORT(h3GetResolution)(H3Index h) { return H3_GET_RESOLUTION(h); } |
37 | |
38 | /** |
39 | * Returns the H3 base cell number of an H3 index. |
40 | * @param h The H3 index. |
41 | * @return The base cell of the H3 index argument. |
42 | */ |
43 | int H3_EXPORT(h3GetBaseCell)(H3Index h) { return H3_GET_BASE_CELL(h); } |
44 | |
45 | /** |
46 | * Converts a string representation of an H3 index into an H3 index. |
47 | * @param str The string representation of an H3 index. |
48 | * @return The H3 index corresponding to the string argument, or 0 if invalid. |
49 | */ |
50 | H3Index H3_EXPORT(stringToH3)(const char* str) { |
51 | H3Index h = H3_INVALID_INDEX; |
52 | // If failed, h will be unmodified and we should return 0 anyways. |
53 | sscanf(str, "%" PRIx64, &h); |
54 | return h; |
55 | } |
56 | |
57 | /** |
58 | * Converts an H3 index into a string representation. |
59 | * @param h The H3 index to convert. |
60 | * @param str The string representation of the H3 index. |
61 | * @param sz Size of the buffer `str` |
62 | */ |
63 | void H3_EXPORT(h3ToString)(H3Index h, char* str, size_t sz) { |
64 | // An unsigned 64 bit integer will be expressed in at most |
65 | // 16 digits plus 1 for the null terminator. |
66 | if (sz < 17) { |
67 | // Buffer is potentially not large enough. |
68 | return; |
69 | } |
70 | sprintf(str, "%" PRIx64, h); |
71 | } |
72 | |
73 | /** |
74 | * Returns whether or not an H3 index is valid. |
75 | * @param h The H3 index to validate. |
76 | * @return 1 if the H3 index if valid, and 0 if it is not. |
77 | */ |
78 | int H3_EXPORT(h3IsValid)(H3Index h) { |
79 | if (H3_GET_MODE(h) != H3_HEXAGON_MODE) return 0; |
80 | |
81 | int baseCell = H3_GET_BASE_CELL(h); |
82 | if (baseCell < 0 || baseCell >= NUM_BASE_CELLS) return 0; |
83 | |
84 | int res = H3_GET_RESOLUTION(h); |
85 | if (res < 0 || res > MAX_H3_RES) return 0; |
86 | |
87 | bool foundFirstNonZeroDigit = false; |
88 | for (int r = 1; r <= res; r++) { |
89 | Direction digit = H3_GET_INDEX_DIGIT(h, r); |
90 | |
91 | if (!foundFirstNonZeroDigit && digit != CENTER_DIGIT) { |
92 | foundFirstNonZeroDigit = true; |
93 | if (_isBaseCellPentagon(baseCell) && digit == K_AXES_DIGIT) { |
94 | return 0; |
95 | } |
96 | } |
97 | |
98 | if (digit < CENTER_DIGIT || digit >= NUM_DIGITS) return 0; |
99 | } |
100 | |
101 | for (int r = res + 1; r <= MAX_H3_RES; r++) { |
102 | Direction digit = H3_GET_INDEX_DIGIT(h, r); |
103 | if (digit != INVALID_DIGIT) return 0; |
104 | } |
105 | |
106 | return 1; |
107 | } |
108 | |
109 | /** |
110 | * Initializes an H3 index. |
111 | * @param hp The H3 index to initialize. |
112 | * @param res The H3 resolution to initialize the index to. |
113 | * @param baseCell The H3 base cell to initialize the index to. |
114 | * @param initDigit The H3 digit (0-7) to initialize all of the index digits to. |
115 | */ |
116 | void setH3Index(H3Index* hp, int res, int baseCell, Direction initDigit) { |
117 | H3Index h = H3_INIT; |
118 | H3_SET_MODE(h, H3_HEXAGON_MODE); |
119 | H3_SET_RESOLUTION(h, res); |
120 | H3_SET_BASE_CELL(h, baseCell); |
121 | for (int r = 1; r <= res; r++) H3_SET_INDEX_DIGIT(h, r, initDigit); |
122 | *hp = h; |
123 | } |
124 | |
125 | /** |
126 | * h3ToParent produces the parent index for a given H3 index |
127 | * |
128 | * @param h H3Index to find parent of |
129 | * @param parentRes The resolution to switch to (parent, grandparent, etc) |
130 | * |
131 | * @return H3Index of the parent, or 0 if you actually asked for a child |
132 | */ |
133 | H3Index H3_EXPORT(h3ToParent)(H3Index h, int parentRes) { |
134 | int childRes = H3_GET_RESOLUTION(h); |
135 | if (parentRes > childRes) { |
136 | return H3_INVALID_INDEX; |
137 | } else if (parentRes == childRes) { |
138 | return h; |
139 | } else if (parentRes < 0 || parentRes > MAX_H3_RES) { |
140 | return H3_INVALID_INDEX; |
141 | } |
142 | H3Index parentH = H3_SET_RESOLUTION(h, parentRes); |
143 | for (int i = parentRes + 1; i <= childRes; i++) { |
144 | H3_SET_INDEX_DIGIT(parentH, i, H3_DIGIT_MASK); |
145 | } |
146 | return parentH; |
147 | } |
148 | |
149 | /** |
150 | * maxH3ToChildrenSize returns the maximum number of children possible for a |
151 | * given child level. |
152 | * |
153 | * @param h H3Index to find the number of children of |
154 | * @param childRes The resolution of the child level you're interested in |
155 | * |
156 | * @return int count of maximum number of children (equal for hexagons, less for |
157 | * pentagons |
158 | */ |
159 | int H3_EXPORT(maxH3ToChildrenSize)(H3Index h, int childRes) { |
160 | int parentRes = H3_GET_RESOLUTION(h); |
161 | if (parentRes > childRes) { |
162 | return 0; |
163 | } |
164 | return _ipow(7, (childRes - parentRes)); |
165 | } |
166 | |
167 | /** |
168 | * makeDirectChild takes an index and immediately returns the immediate child |
169 | * index based on the specified cell number. Bit operations only, could generate |
170 | * invalid indexes if not careful (deleted cell under a pentagon). |
171 | * |
172 | * @param h H3Index to find the direct child of |
173 | * @param cellNumber int id of the direct child (0-6) |
174 | * |
175 | * @return The new H3Index for the child |
176 | */ |
177 | H3Index makeDirectChild(H3Index h, int cellNumber) { |
178 | int childRes = H3_GET_RESOLUTION(h) + 1; |
179 | H3Index childH = H3_SET_RESOLUTION(h, childRes); |
180 | H3_SET_INDEX_DIGIT(childH, childRes, cellNumber); |
181 | return childH; |
182 | } |
183 | |
184 | /** |
185 | * h3ToChildren takes the given hexagon id and generates all of the children |
186 | * at the specified resolution storing them into the provided memory pointer. |
187 | * It's assumed that maxH3ToChildrenSize was used to determine the allocation. |
188 | * |
189 | * @param h H3Index to find the children of |
190 | * @param childRes int the child level to produce |
191 | * @param children H3Index* the memory to store the resulting addresses in |
192 | */ |
193 | void H3_EXPORT(h3ToChildren)(H3Index h, int childRes, H3Index* children) { |
194 | int parentRes = H3_GET_RESOLUTION(h); |
195 | if (parentRes > childRes) { |
196 | return; |
197 | } else if (parentRes == childRes) { |
198 | *children = h; |
199 | return; |
200 | } |
201 | int bufferSize = H3_EXPORT(maxH3ToChildrenSize)(h, childRes); |
202 | int bufferChildStep = (bufferSize / 7); |
203 | int isAPentagon = H3_EXPORT(h3IsPentagon)(h); |
204 | for (int i = 0; i < 7; i++) { |
205 | if (isAPentagon && i == K_AXES_DIGIT) { |
206 | H3Index* nextChild = children + bufferChildStep; |
207 | while (children < nextChild) { |
208 | *children = H3_INVALID_INDEX; |
209 | children++; |
210 | } |
211 | } else { |
212 | H3_EXPORT(h3ToChildren)(makeDirectChild(h, i), childRes, children); |
213 | children += bufferChildStep; |
214 | } |
215 | } |
216 | } |
217 | |
218 | /** |
219 | * compact takes a set of hexagons all at the same resolution and compresses |
220 | * them by pruning full child branches to the parent level. This is also done |
221 | * for all parents recursively to get the minimum number of hex addresses that |
222 | * perfectly cover the defined space. |
223 | * @param h3Set Set of hexagons |
224 | * @param compactedSet The output array of compressed hexagons (preallocated) |
225 | * @param numHexes The size of the input and output arrays (possible that no |
226 | * contiguous regions exist in the set at all and no compression possible) |
227 | * @return an error code on bad input data |
228 | */ |
229 | int H3_EXPORT(compact)(const H3Index* h3Set, H3Index* compactedSet, |
230 | const int numHexes) { |
231 | int res = H3_GET_RESOLUTION(h3Set[0]); |
232 | if (res == 0) { |
233 | // No compaction possible, just copy the set to output |
234 | for (int i = 0; i < numHexes; i++) { |
235 | compactedSet[i] = h3Set[i]; |
236 | } |
237 | return 0; |
238 | } |
239 | H3Index* remainingHexes = malloc(numHexes * sizeof(H3Index)); |
240 | memcpy(remainingHexes, h3Set, numHexes * sizeof(H3Index)); |
241 | H3Index* hashSetArray = calloc(numHexes, sizeof(H3Index)); |
242 | H3Index* compactedSetOffset = compactedSet; |
243 | int numRemainingHexes = numHexes; |
244 | while (numRemainingHexes) { |
245 | res = H3_GET_RESOLUTION(remainingHexes[0]); |
246 | int parentRes = res - 1; |
247 | // Put the parents of the hexagons into the temp array |
248 | // via a hashing mechanism, and use the reserved bits |
249 | // to track how many times a parent is duplicated |
250 | for (int i = 0; i < numRemainingHexes; i++) { |
251 | H3Index currIndex = remainingHexes[i]; |
252 | if (currIndex != 0) { |
253 | H3Index parent = H3_EXPORT(h3ToParent)(currIndex, parentRes); |
254 | // Modulus hash the parent into the temp array |
255 | int loc = (int)(parent % numRemainingHexes); |
256 | int loopCount = 0; |
257 | while (hashSetArray[loc] != 0) { |
258 | if (loopCount > numRemainingHexes) { |
259 | // LCOV_EXCL_START |
260 | // This case should not be possible because at most one |
261 | // index is placed into hashSetArray per |
262 | // numRemainingHexes. |
263 | free(remainingHexes); |
264 | free(hashSetArray); |
265 | return -1; |
266 | // LCOV_EXCL_STOP |
267 | } |
268 | H3Index tempIndex = |
269 | hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; |
270 | if (tempIndex == parent) { |
271 | int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; |
272 | if (count > 7) { |
273 | // Only possible on duplicate input |
274 | free(remainingHexes); |
275 | free(hashSetArray); |
276 | return -2; |
277 | } |
278 | H3_SET_RESERVED_BITS(parent, count); |
279 | hashSetArray[loc] = H3_INVALID_INDEX; |
280 | } else { |
281 | loc = (loc + 1) % numRemainingHexes; |
282 | } |
283 | loopCount++; |
284 | } |
285 | hashSetArray[loc] = parent; |
286 | } |
287 | } |
288 | // Determine which parent hexagons have a complete set |
289 | // of children and put them in the compactableHexes array |
290 | int compactableCount = 0; |
291 | int maxCompactableCount = |
292 | numRemainingHexes / 6; // Somehow all pentagons; conservative |
293 | if (maxCompactableCount == 0) { |
294 | memcpy(compactedSetOffset, remainingHexes, |
295 | numRemainingHexes * sizeof(remainingHexes[0])); |
296 | break; |
297 | } |
298 | H3Index* compactableHexes = |
299 | malloc(maxCompactableCount * sizeof(H3Index)); |
300 | for (int i = 0; i < numRemainingHexes; i++) { |
301 | if (hashSetArray[i] == 0) continue; |
302 | int count = H3_GET_RESERVED_BITS(hashSetArray[i]) + 1; |
303 | // Include the deleted direction for pentagons as implicitly "there" |
304 | if (H3_EXPORT(h3IsPentagon)(hashSetArray[i] & |
305 | H3_RESERVED_MASK_NEGATIVE)) { |
306 | // We need this later on, no need to recalculate |
307 | H3_SET_RESERVED_BITS(hashSetArray[i], count); |
308 | // Increment count after setting the reserved bits, |
309 | // since count is already incremented above, so it |
310 | // will be the expected value for a complete hexagon. |
311 | count++; |
312 | } |
313 | if (count == 7) { |
314 | // Bingo! Full set! |
315 | compactableHexes[compactableCount] = |
316 | hashSetArray[i] & H3_RESERVED_MASK_NEGATIVE; |
317 | compactableCount++; |
318 | } |
319 | } |
320 | // Uncompactable hexes are immediately copied into the |
321 | // output compactedSetOffset |
322 | int uncompactableCount = 0; |
323 | for (int i = 0; i < numRemainingHexes; i++) { |
324 | H3Index currIndex = remainingHexes[i]; |
325 | if (currIndex != H3_INVALID_INDEX) { |
326 | H3Index parent = H3_EXPORT(h3ToParent)(currIndex, parentRes); |
327 | // Modulus hash the parent into the temp array |
328 | // to determine if this index was included in |
329 | // the compactableHexes array |
330 | int loc = (int)(parent % numRemainingHexes); |
331 | int loopCount = 0; |
332 | bool isUncompactable = true; |
333 | do { |
334 | if (loopCount > numRemainingHexes) { |
335 | // LCOV_EXCL_START |
336 | // This case should not be possible because at most one |
337 | // index is placed into hashSetArray per input hexagon. |
338 | free(compactableHexes); |
339 | free(remainingHexes); |
340 | free(hashSetArray); |
341 | return -1; // Only possible on duplicate input |
342 | // LCOV_EXCL_STOP |
343 | } |
344 | H3Index tempIndex = |
345 | hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; |
346 | if (tempIndex == parent) { |
347 | int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; |
348 | if (count == 7) { |
349 | isUncompactable = false; |
350 | } |
351 | break; |
352 | } else { |
353 | loc = (loc + 1) % numRemainingHexes; |
354 | } |
355 | loopCount++; |
356 | } while (hashSetArray[loc] != parent); |
357 | if (isUncompactable) { |
358 | compactedSetOffset[uncompactableCount] = remainingHexes[i]; |
359 | uncompactableCount++; |
360 | } |
361 | } |
362 | } |
363 | // Set up for the next loop |
364 | memset(hashSetArray, 0, numHexes * sizeof(H3Index)); |
365 | compactedSetOffset += uncompactableCount; |
366 | memcpy(remainingHexes, compactableHexes, |
367 | compactableCount * sizeof(H3Index)); |
368 | numRemainingHexes = compactableCount; |
369 | free(compactableHexes); |
370 | } |
371 | free(remainingHexes); |
372 | free(hashSetArray); |
373 | return 0; |
374 | } |
375 | |
376 | /** |
377 | * uncompact takes a compressed set of hexagons and expands back to the |
378 | * original set of hexagons. |
379 | * @param compactedSet Set of hexagons |
380 | * @param numHexes The number of hexes in the input set |
381 | * @param h3Set Output array of decompressed hexagons (preallocated) |
382 | * @param maxHexes The size of the output array to bound check against |
383 | * @param res The hexagon resolution to decompress to |
384 | * @return An error code if output array is too small or any hexagon is |
385 | * smaller than the output resolution. |
386 | */ |
387 | int H3_EXPORT(uncompact)(const H3Index* compactedSet, const int numHexes, |
388 | H3Index* h3Set, const int maxHexes, const int res) { |
389 | int outOffset = 0; |
390 | for (int i = 0; i < numHexes; i++) { |
391 | if (outOffset >= maxHexes) { |
392 | // We went too far, abort! |
393 | return -1; |
394 | } |
395 | if (compactedSet[i] == 0) continue; |
396 | int currentRes = H3_GET_RESOLUTION(compactedSet[i]); |
397 | if (currentRes > res) { |
398 | // Nonsensical. Abort. |
399 | return -2; |
400 | } |
401 | if (currentRes == res) { |
402 | // Just copy and move along |
403 | h3Set[outOffset] = compactedSet[i]; |
404 | outOffset++; |
405 | } else { |
406 | // Bigger hexagon to reduce in size |
407 | int numHexesToGen = |
408 | H3_EXPORT(maxH3ToChildrenSize)(compactedSet[i], res); |
409 | if (outOffset + numHexesToGen > maxHexes) { |
410 | // We're about to go too far, abort! |
411 | return -1; |
412 | } |
413 | H3_EXPORT(h3ToChildren)(compactedSet[i], res, h3Set + outOffset); |
414 | outOffset += numHexesToGen; |
415 | } |
416 | } |
417 | return 0; |
418 | } |
419 | |
420 | /** |
421 | * maxUncompactSize takes a compacted set of hexagons are provides an |
422 | * upper-bound estimate of the size of the uncompacted set of hexagons. |
423 | * @param compactedSet Set of hexagons |
424 | * @param numHexes The number of hexes in the input set |
425 | * @param res The hexagon resolution to decompress to |
426 | * @return The number of hexagons to allocate memory for, or a negative |
427 | * number if an error occurs. |
428 | */ |
429 | int H3_EXPORT(maxUncompactSize)(const H3Index* compactedSet, const int numHexes, |
430 | const int res) { |
431 | int maxNumHexagons = 0; |
432 | for (int i = 0; i < numHexes; i++) { |
433 | if (compactedSet[i] == 0) continue; |
434 | int currentRes = H3_GET_RESOLUTION(compactedSet[i]); |
435 | if (currentRes > res) { |
436 | // Nonsensical. Abort. |
437 | return -1; |
438 | } |
439 | if (currentRes == res) { |
440 | maxNumHexagons++; |
441 | } else { |
442 | // Bigger hexagon to reduce in size |
443 | int numHexesToGen = |
444 | H3_EXPORT(maxH3ToChildrenSize)(compactedSet[i], res); |
445 | maxNumHexagons += numHexesToGen; |
446 | } |
447 | } |
448 | return maxNumHexagons; |
449 | } |
450 | |
451 | /** |
452 | * h3IsResClassIII takes a hexagon ID and determines if it is in a |
453 | * Class III resolution (rotated versus the icosahedron and subject |
454 | * to shape distortion adding extra points on icosahedron edges, making |
455 | * them not true hexagons). |
456 | * @param h The H3Index to check. |
457 | * @return Returns 1 if the hexagon is class III, otherwise 0. |
458 | */ |
459 | int H3_EXPORT(h3IsResClassIII)(H3Index h) { return H3_GET_RESOLUTION(h) % 2; } |
460 | |
461 | /** |
462 | * h3IsPentagon takes an H3Index and determines if it is actually a |
463 | * pentagon. |
464 | * @param h The H3Index to check. |
465 | * @return Returns 1 if it is a pentagon, otherwise 0. |
466 | */ |
467 | int H3_EXPORT(h3IsPentagon)(H3Index h) { |
468 | return _isBaseCellPentagon(H3_GET_BASE_CELL(h)) && |
469 | !_h3LeadingNonZeroDigit(h); |
470 | } |
471 | |
472 | /** |
473 | * Returns the highest resolution non-zero digit in an H3Index. |
474 | * @param h The H3Index. |
475 | * @return The highest resolution non-zero digit in the H3Index. |
476 | */ |
477 | Direction _h3LeadingNonZeroDigit(H3Index h) { |
478 | for (int r = 1; r <= H3_GET_RESOLUTION(h); r++) |
479 | if (H3_GET_INDEX_DIGIT(h, r)) return H3_GET_INDEX_DIGIT(h, r); |
480 | |
481 | // if we're here it's all 0's |
482 | return CENTER_DIGIT; |
483 | } |
484 | |
485 | /** |
486 | * Rotate an H3Index 60 degrees counter-clockwise about a pentagonal center. |
487 | * @param h The H3Index. |
488 | */ |
489 | H3Index _h3RotatePent60ccw(H3Index h) { |
490 | // rotate in place; skips any leading 1 digits (k-axis) |
491 | |
492 | int foundFirstNonZeroDigit = 0; |
493 | for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { |
494 | // rotate this digit |
495 | H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(H3_GET_INDEX_DIGIT(h, r))); |
496 | |
497 | // look for the first non-zero digit so we |
498 | // can adjust for deleted k-axes sequence |
499 | // if necessary |
500 | if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { |
501 | foundFirstNonZeroDigit = 1; |
502 | |
503 | // adjust for deleted k-axes sequence |
504 | if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) |
505 | h = _h3Rotate60ccw(h); |
506 | } |
507 | } |
508 | return h; |
509 | } |
510 | |
511 | /** |
512 | * Rotate an H3Index 60 degrees clockwise about a pentagonal center. |
513 | * @param h The H3Index. |
514 | */ |
515 | H3Index _h3RotatePent60cw(H3Index h) { |
516 | // rotate in place; skips any leading 1 digits (k-axis) |
517 | |
518 | int foundFirstNonZeroDigit = 0; |
519 | for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { |
520 | // rotate this digit |
521 | H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); |
522 | |
523 | // look for the first non-zero digit so we |
524 | // can adjust for deleted k-axes sequence |
525 | // if necessary |
526 | if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { |
527 | foundFirstNonZeroDigit = 1; |
528 | |
529 | // adjust for deleted k-axes sequence |
530 | if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) h = _h3Rotate60cw(h); |
531 | } |
532 | } |
533 | return h; |
534 | } |
535 | |
536 | /** |
537 | * Rotate an H3Index 60 degrees counter-clockwise. |
538 | * @param h The H3Index. |
539 | */ |
540 | H3Index _h3Rotate60ccw(H3Index h) { |
541 | for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { |
542 | Direction oldDigit = H3_GET_INDEX_DIGIT(h, r); |
543 | H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(oldDigit)); |
544 | } |
545 | |
546 | return h; |
547 | } |
548 | |
549 | /** |
550 | * Rotate an H3Index 60 degrees clockwise. |
551 | * @param h The H3Index. |
552 | */ |
553 | H3Index _h3Rotate60cw(H3Index h) { |
554 | for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { |
555 | H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); |
556 | } |
557 | |
558 | return h; |
559 | } |
560 | |
561 | /** |
562 | * Convert an FaceIJK address to the corresponding H3Index. |
563 | * @param fijk The FaceIJK address. |
564 | * @param res The cell resolution. |
565 | * @return The encoded H3Index (or 0 on failure). |
566 | */ |
567 | H3Index _faceIjkToH3(const FaceIJK* fijk, int res) { |
568 | // initialize the index |
569 | H3Index h = H3_INIT; |
570 | H3_SET_MODE(h, H3_HEXAGON_MODE); |
571 | H3_SET_RESOLUTION(h, res); |
572 | |
573 | // check for res 0/base cell |
574 | if (res == 0) { |
575 | if (fijk->coord.i > MAX_FACE_COORD || fijk->coord.j > MAX_FACE_COORD || |
576 | fijk->coord.k > MAX_FACE_COORD) { |
577 | // out of range input |
578 | return H3_INVALID_INDEX; |
579 | } |
580 | |
581 | H3_SET_BASE_CELL(h, _faceIjkToBaseCell(fijk)); |
582 | return h; |
583 | } |
584 | |
585 | // we need to find the correct base cell FaceIJK for this H3 index; |
586 | // start with the passed in face and resolution res ijk coordinates |
587 | // in that face's coordinate system |
588 | FaceIJK fijkBC = *fijk; |
589 | |
590 | // build the H3Index from finest res up |
591 | // adjust r for the fact that the res 0 base cell offsets the indexing |
592 | // digits |
593 | CoordIJK* ijk = &fijkBC.coord; |
594 | for (int r = res - 1; r >= 0; r--) { |
595 | CoordIJK lastIJK = *ijk; |
596 | CoordIJK lastCenter; |
597 | if (isResClassIII(r + 1)) { |
598 | // rotate ccw |
599 | _upAp7(ijk); |
600 | lastCenter = *ijk; |
601 | _downAp7(&lastCenter); |
602 | } else { |
603 | // rotate cw |
604 | _upAp7r(ijk); |
605 | lastCenter = *ijk; |
606 | _downAp7r(&lastCenter); |
607 | } |
608 | |
609 | CoordIJK diff; |
610 | _ijkSub(&lastIJK, &lastCenter, &diff); |
611 | _ijkNormalize(&diff); |
612 | |
613 | H3_SET_INDEX_DIGIT(h, r + 1, _unitIjkToDigit(&diff)); |
614 | } |
615 | |
616 | // fijkBC should now hold the IJK of the base cell in the |
617 | // coordinate system of the current face |
618 | |
619 | if (fijkBC.coord.i > MAX_FACE_COORD || fijkBC.coord.j > MAX_FACE_COORD || |
620 | fijkBC.coord.k > MAX_FACE_COORD) { |
621 | // out of range input |
622 | return H3_INVALID_INDEX; |
623 | } |
624 | |
625 | // lookup the correct base cell |
626 | int baseCell = _faceIjkToBaseCell(&fijkBC); |
627 | H3_SET_BASE_CELL(h, baseCell); |
628 | |
629 | // rotate if necessary to get canonical base cell orientation |
630 | // for this base cell |
631 | int numRots = _faceIjkToBaseCellCCWrot60(&fijkBC); |
632 | if (_isBaseCellPentagon(baseCell)) { |
633 | // force rotation out of missing k-axes sub-sequence |
634 | if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) { |
635 | // check for a cw/ccw offset face; default is ccw |
636 | if (_baseCellIsCwOffset(baseCell, fijkBC.face)) { |
637 | h = _h3Rotate60cw(h); |
638 | } else { |
639 | h = _h3Rotate60ccw(h); |
640 | } |
641 | } |
642 | |
643 | for (int i = 0; i < numRots; i++) h = _h3RotatePent60ccw(h); |
644 | } else { |
645 | for (int i = 0; i < numRots; i++) { |
646 | h = _h3Rotate60ccw(h); |
647 | } |
648 | } |
649 | |
650 | return h; |
651 | } |
652 | |
653 | /** |
654 | * Encodes a coordinate on the sphere to the H3 index of the containing cell at |
655 | * the specified resolution. |
656 | * |
657 | * Returns 0 on invalid input. |
658 | * |
659 | * @param g The spherical coordinates to encode. |
660 | * @param res The desired H3 resolution for the encoding. |
661 | * @return The encoded H3Index (or 0 on failure). |
662 | */ |
663 | H3Index H3_EXPORT(geoToH3)(const GeoCoord* g, int res) { |
664 | if (res < 0 || res > MAX_H3_RES) { |
665 | return H3_INVALID_INDEX; |
666 | } |
667 | if (!isfinite(g->lat) || !isfinite(g->lon)) { |
668 | return H3_INVALID_INDEX; |
669 | } |
670 | |
671 | FaceIJK fijk; |
672 | _geoToFaceIjk(g, res, &fijk); |
673 | return _faceIjkToH3(&fijk, res); |
674 | } |
675 | |
676 | /** |
677 | * Convert an H3Index to the FaceIJK address on a specified icosahedral face. |
678 | * @param h The H3Index. |
679 | * @param fijk The FaceIJK address, initialized with the desired face |
680 | * and normalized base cell coordinates. |
681 | * @return Returns 1 if the possibility of overage exists, otherwise 0. |
682 | */ |
683 | int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK* fijk) { |
684 | CoordIJK* ijk = &fijk->coord; |
685 | int res = H3_GET_RESOLUTION(h); |
686 | |
687 | // center base cell hierarchy is entirely on this face |
688 | int possibleOverage = 1; |
689 | if (!_isBaseCellPentagon(H3_GET_BASE_CELL(h)) && |
690 | (res == 0 || |
691 | (fijk->coord.i == 0 && fijk->coord.j == 0 && fijk->coord.k == 0))) |
692 | possibleOverage = 0; |
693 | |
694 | for (int r = 1; r <= res; r++) { |
695 | if (isResClassIII(r)) { |
696 | // Class III == rotate ccw |
697 | _downAp7(ijk); |
698 | } else { |
699 | // Class II == rotate cw |
700 | _downAp7r(ijk); |
701 | } |
702 | |
703 | _neighbor(ijk, H3_GET_INDEX_DIGIT(h, r)); |
704 | } |
705 | |
706 | return possibleOverage; |
707 | } |
708 | |
709 | /** |
710 | * Convert an H3Index to a FaceIJK address. |
711 | * @param h The H3Index. |
712 | * @param fijk The corresponding FaceIJK address. |
713 | */ |
714 | void _h3ToFaceIjk(H3Index h, FaceIJK* fijk) { |
715 | int baseCell = H3_GET_BASE_CELL(h); |
716 | // adjust for the pentagonal missing sequence; all of sub-sequence 5 needs |
717 | // to be adjusted (and some of sub-sequence 4 below) |
718 | if (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 5) |
719 | h = _h3Rotate60cw(h); |
720 | |
721 | // start with the "home" face and ijk+ coordinates for the base cell of c |
722 | *fijk = baseCellData[baseCell].homeFijk; |
723 | if (!_h3ToFaceIjkWithInitializedFijk(h, fijk)) |
724 | return; // no overage is possible; h lies on this face |
725 | |
726 | // if we're here we have the potential for an "overage"; i.e., it is |
727 | // possible that c lies on an adjacent face |
728 | |
729 | CoordIJK origIJK = fijk->coord; |
730 | |
731 | // if we're in Class III, drop into the next finer Class II grid |
732 | int res = H3_GET_RESOLUTION(h); |
733 | if (isResClassIII(res)) { |
734 | // Class III |
735 | _downAp7r(&fijk->coord); |
736 | res++; |
737 | } |
738 | |
739 | // adjust for overage if needed |
740 | // a pentagon base cell with a leading 4 digit requires special handling |
741 | int pentLeading4 = |
742 | (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 4); |
743 | if (_adjustOverageClassII(fijk, res, pentLeading4, 0)) { |
744 | // if the base cell is a pentagon we have the potential for secondary |
745 | // overages |
746 | if (_isBaseCellPentagon(baseCell)) { |
747 | while (1) { |
748 | if (!_adjustOverageClassII(fijk, res, 0, 0)) break; |
749 | } |
750 | } |
751 | |
752 | if (res != H3_GET_RESOLUTION(h)) _upAp7r(&fijk->coord); |
753 | } else if (res != H3_GET_RESOLUTION(h)) { |
754 | fijk->coord = origIJK; |
755 | } |
756 | } |
757 | |
758 | /** |
759 | * Determines the spherical coordinates of the center point of an H3 index. |
760 | * |
761 | * @param h3 The H3 index. |
762 | * @param g The spherical coordinates of the H3 cell center. |
763 | */ |
764 | void H3_EXPORT(h3ToGeo)(H3Index h3, GeoCoord* g) { |
765 | FaceIJK fijk; |
766 | _h3ToFaceIjk(h3, &fijk); |
767 | _faceIjkToGeo(&fijk, H3_GET_RESOLUTION(h3), g); |
768 | } |
769 | |
770 | /** |
771 | * Determines the cell boundary in spherical coordinates for an H3 index. |
772 | * |
773 | * @param h3 The H3 index. |
774 | * @param gb The boundary of the H3 cell in spherical coordinates. |
775 | */ |
776 | void H3_EXPORT(h3ToGeoBoundary)(H3Index h3, GeoBoundary* gb) { |
777 | FaceIJK fijk; |
778 | _h3ToFaceIjk(h3, &fijk); |
779 | _faceIjkToGeoBoundary(&fijk, H3_GET_RESOLUTION(h3), |
780 | H3_EXPORT(h3IsPentagon)(h3), gb); |
781 | } |
782 | |
783 | /** |
784 | * Returns whether or not a resolution is a Class III grid. Note that odd |
785 | * resolutions are Class III and even resolutions are Class II. |
786 | * @param res The H3 resolution. |
787 | * @return 1 if the resolution is a Class III grid, and 0 if the resolution is |
788 | * a Class II grid. |
789 | */ |
790 | int isResClassIII(int res) { return res % 2; } |
791 | |