h3_h3Index.c (746 lines of code) (raw):

/* * Copyright 2016-2021, 2024 Uber Technologies, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** @file h3Index.c * @brief H3Index utility functions * (see h3api.h for the main library entry functions) */ #include "h3_h3Index.h" #include <inttypes.h> #include <math.h> #include <stdlib.h> #include <string.h> #include "h3_alloc.h" #include "h3_baseCells.h" #include "h3_faceijk.h" #include "h3_h3Assert.h" #include "h3_iterators.h" #include "h3_mathExtensions.h" /** @var H3ErrorDescriptions * @brief An array of strings describing each of the H3ErrorCodes enum values */ static char *H3ErrorDescriptions[] = { /* E_SUCCESS */ "Success", /* E_FAILED */ "The operation failed but a more specific error is not available", /* E_DOMAIN */ "Argument was outside of acceptable range", /* E_LATLNG_DOMAIN */ "Latitude or longitude arguments were outside of acceptable range", /* E_RES_DOMAIN */ "Resolution argument was outside of acceptable range", /* E_CELL_INVALID */ "Cell argument was not valid", /* E_DIR_EDGE_INVALID */ "Directed edge argument was not valid", /* E_UNDIR_EDGE_INVALID */ "Undirected edge argument was not valid", /* E_VERTEX_INVALID */ "Vertex argument was not valid", /* E_PENTAGON */ "Pentagon distortion was encountered", /* E_DUPLICATE_INPUT */ "Duplicate input", /* E_NOT_NEIGHBORS */ "Cell arguments were not neighbors", /* E_RES_MISMATCH */ "Cell arguments had incompatible resolutions", /* E_MEMORY_ALLOC */ "Memory allocation failed", /* E_MEMORY_BOUNDS */ "Bounds of provided memory were insufficient", /* E_OPTION_INVALID */ "Mode or flags argument was not valid"}; /** * Returns the string describing the H3Error. This string is internally * allocated and should not be `free`d. * @param err The H3 error. * @return The string describing the H3Error */ const char *H3_EXPORT(describeH3Error)(H3Error err) { if (err >= 0 && err <= 15) { // TODO: Better way to bounds check here? return H3ErrorDescriptions[err]; } else { return "Invalid error code"; } } /** * Returns the H3 resolution of an H3 index. * @param h The H3 index. * @return The resolution of the H3 index argument. */ int H3_EXPORT(getResolution)(H3Index h) { return H3_GET_RESOLUTION(h); } /** * Returns the H3 base cell "number" of an H3 cell (hexagon or pentagon). * * Note: Technically works on H3 edges, but will return base cell of the * origin cell. * * @param h The H3 cell. * @return The base cell "number" of the H3 cell argument. */ int H3_EXPORT(getBaseCellNumber)(H3Index h) { return H3_GET_BASE_CELL(h); } /** * Converts a string representation of an H3 index into an H3 index. * @param str The string representation of an H3 index. * @param out Output: The H3 index corresponding to the string argument */ H3Error H3_EXPORT(stringToH3)(const char *str, H3Index *out) { H3Index h = H3_NULL; // If failed, h will be unmodified and we should return H3_NULL anyways. int read = sscanf(str, "%" PRIx64, &h); if (read != 1) { return E_FAILED; } *out = h; return E_SUCCESS; } /** * Converts an H3 index into a string representation. * @param h The H3 index to convert. * @param str The string representation of the H3 index. * @param sz Size of the buffer `str` */ H3Error H3_EXPORT(h3ToString)(H3Index h, char *str, size_t sz) { // An unsigned 64 bit integer will be expressed in at most // 16 digits plus 1 for the null terminator. if (sz < 17) { // Buffer is potentially not large enough. return E_MEMORY_BOUNDS; } sprintf(str, "%" PRIx64, h); return E_SUCCESS; } /* The top 8 bits of any cell should be a specific constant: - The 1 high bit should be `0` - The 4 mode bits should be `0001` (H3_CELL_MODE) - The 3 reserved bits should be `000` In total, the top 8 bits should be `0_0001_000` */ static inline bool _hasGoodTopBits(H3Index h) { h >>= (64 - 8); return h == 0b00001000; } /* Check that no digit from 1 to `res` is 7 (INVALID_DIGIT). MHI = 0b100100100100100100100100100100100100100100100; MLO = MHI >> 2; | d | d & MHI | ~d | ~d - MLO | d & MHI & (~d - MLO) | result | |-----|---------|-----|----------|----------------------|---------| | 000 | 000 | | | 000 | OK | | 001 | 000 | | | 000 | OK | | 010 | 000 | | | 000 | OK | | 011 | 000 | | | 000 | OK | | 100 | 100 | 011 | 010 | 000 | OK | | 101 | 100 | 010 | 001 | 000 | OK | | 110 | 100 | 001 | 000 | 000 | OK | | 111 | 100 | 000 | 111* | 100 | invalid | *: carry happened Note: only care about identifying the *lowest* 7. Examples with multiple digits: | d | d & MHI | ~d | ~d - MLO | d & MHI & (~d - MLO) | result | |---------|---------|---------|----------|----------------------|---------| | 111.111 | 100.100 | 000.000 | 110.111* | 100.100 | invalid | | 110.111 | 100.100 | 001.000 | 111.111* | 100.100 | invalid | | 110.110 | 100.100 | 001.001 | 000.000 | 000.000 | OK | *: carry happened In the second example with 110.111, we "misidentify" the 110 as a 7, due to a carry from the lower bits. But this is OK because we correctly identify the lowest (only, in this example) 7 just before it. We only have to worry about carries affecting higher bits in the case of a 7; all other digits (0--6) don't cause a carry when computing ~d - MLO. So even though a 7 can affect the results of higher bits, this is OK because we will always correctly identify the lowest 7. For further notes, see the discussion here: https://github.com/uber/h3/pull/496#discussion_r795851046 */ static inline bool _hasAny7UptoRes(H3Index h, int res) { const uint64_t MHI = 0b100100100100100100100100100100100100100100100; const uint64_t MLO = MHI >> 2; int shift = 3 * (15 - res); h >>= shift; h <<= shift; h = (h & MHI & (~h - MLO)); return h != 0; } /* Check that all unused digits after `res` are set to 7 (INVALID_DIGIT). Bit shift to avoid looping through digits. */ static inline bool _hasAll7AfterRes(H3Index h, int res) { // NOTE: res check is needed because we can't shift by 64 if (res < 15) { int shift = 19 + 3 * res; h = ~h; h <<= shift; h >>= shift; return h == 0; } return true; } /* Get index of first nonzero bit of an H3Index. When available, use compiler intrinsics, which should be fast. If not available, fall back to a loop. */ static inline int _firstOneIndex(H3Index h) { #if defined(__GNUC__) || defined(__clang__) return 63 - __builtin_clzll(h); #elif defined(_MSC_VER) && defined(_M_X64) // doesn't work on win32 unsigned long index; _BitScanReverse64(&index, h); return (int)index; #else // Portable fallback int pos = 63 - 19; H3Index m = 1; while ((h & (m << pos)) == 0) pos--; return pos; #endif } /* One final validation just for cells whose base cell (res 0) is a pentagon. Pentagon cells start with a sequence of 0's (CENTER_DIGIT's). The first nonzero digit can't be a 1 (i.e., "deleted subsequence", PENTAGON_SKIPPED_DIGIT, or K_AXES_DIGIT). We can check that (in the lower 45 = 15*3 bits) the position of the first 1 bit isn't divisible by 3. */ static inline bool _hasDeletedSubsequence(H3Index h, int base_cell) { // TODO: https://github.com/uber/h3/issues/984 static const bool isBaseCellPentagonArr[128] = { [4] = 1, [14] = 1, [24] = 1, [38] = 1, [49] = 1, [58] = 1, [63] = 1, [72] = 1, [83] = 1, [97] = 1, [107] = 1, [117] = 1}; if (isBaseCellPentagonArr[base_cell]) { h <<= 19; h >>= 19; if (h == 0) return false; // all zeros: res 15 pentagon return _firstOneIndex(h) % 3 == 0; } return false; } /** * Returns whether or not an H3 index is a valid cell (hexagon or pentagon). * @param h The H3 index to validate. * @return 1 if the H3 index if valid, and 0 if it is not. */ int H3_EXPORT(isValidCell)(H3Index h) { /* Look for bit patterns that would disqualify an H3Index from being valid. If identified, exit early. For reference the H3 index bit layout: | Region | # bits | |------------|--------| | High | 1 | | Mode | 4 | | Reserved | 3 | | Resolution | 4 | | Base Cell | 7 | | Digit 1 | 3 | | Digit 2 | 3 | | ... | ... | | Digit 15 | 3 | Speed benefits come from using bit manipulation instead of loops, whenever possible. */ if (!_hasGoodTopBits(h)) return false; // No need to check resolution; any 4 bits give a valid resolution. const int res = H3_GET_RESOLUTION(h); // Get base cell number and check that it is valid. const int bc = H3_GET_BASE_CELL(h); if (bc >= NUM_BASE_CELLS) return false; if (_hasAny7UptoRes(h, res)) return false; if (!_hasAll7AfterRes(h, res)) return false; if (_hasDeletedSubsequence(h, bc)) return false; // If no disqualifications were identified, the index is a valid H3 cell. return true; } /** * Initializes an H3 index. * @param hp The H3 index to initialize. * @param res The H3 resolution to initialize the index to. * @param baseCell The H3 base cell to initialize the index to. * @param initDigit The H3 digit (0-7) to initialize all of the index digits to. */ void setH3Index(H3Index *hp, int res, int baseCell, Direction initDigit) { H3Index h = H3_INIT; H3_SET_MODE(h, H3_CELL_MODE); H3_SET_RESOLUTION(h, res); H3_SET_BASE_CELL(h, baseCell); for (int r = 1; r <= res; r++) H3_SET_INDEX_DIGIT(h, r, initDigit); *hp = h; } /** * cellToParent produces the parent index for a given H3 index * * @param h H3Index to find parent of * @param parentRes The resolution to switch to (parent, grandparent, etc) * @param out Output: H3Index of the parent */ H3Error H3_EXPORT(cellToParent)(H3Index h, int parentRes, H3Index *out) { int childRes = H3_GET_RESOLUTION(h); if (parentRes < 0 || parentRes > MAX_H3_RES) { return E_RES_DOMAIN; } else if (parentRes > childRes) { return E_RES_MISMATCH; } else if (parentRes == childRes) { *out = h; return E_SUCCESS; } H3Index parentH = H3_SET_RESOLUTION(h, parentRes); for (int i = parentRes + 1; i <= childRes; i++) { H3_SET_INDEX_DIGIT(parentH, i, H3_DIGIT_MASK); } *out = parentH; return E_SUCCESS; } /** * Determines whether one resolution is a valid child resolution for a cell. * Each resolution is considered a valid child resolution of itself. * * @param h h3Index parent cell * @param childRes int resolution of the child * * @return The validity of the child resolution */ static bool _hasChildAtRes(H3Index h, int childRes) { int parentRes = H3_GET_RESOLUTION(h); if (childRes < parentRes || childRes > MAX_H3_RES) { return false; } return true; } /** * cellToChildrenSize returns the exact number of children for a cell at a * given child resolution. * * @param h H3Index to find the number of children of * @param childRes The child resolution you're interested in * @param out Output: exact number of children (handles hexagons and * pentagons correctly) */ H3Error H3_EXPORT(cellToChildrenSize)(H3Index h, int childRes, int64_t *out) { if (!_hasChildAtRes(h, childRes)) return E_RES_DOMAIN; int n = childRes - H3_GET_RESOLUTION(h); if (H3_EXPORT(isPentagon)(h)) { *out = 1 + 5 * (_ipow(7, n) - 1) / 6; } else { *out = _ipow(7, n); } return E_SUCCESS; } /** * makeDirectChild takes an index and immediately returns the immediate child * index based on the specified cell number. Bit operations only, could generate * invalid indexes if not careful (deleted cell under a pentagon). * * @param h H3Index to find the direct child of * @param cellNumber int id of the direct child (0-6) * * @return The new H3Index for the child */ H3Index makeDirectChild(H3Index h, int cellNumber) { int childRes = H3_GET_RESOLUTION(h) + 1; H3Index childH = H3_SET_RESOLUTION(h, childRes); H3_SET_INDEX_DIGIT(childH, childRes, cellNumber); return childH; } /** * cellToChildren takes the given hexagon id and generates all of the children * at the specified resolution storing them into the provided memory pointer. * It's assumed that cellToChildrenSize was used to determine the allocation. * * @param h H3Index to find the children of * @param childRes int the child level to produce * @param children H3Index* the memory to store the resulting addresses in */ H3Error H3_EXPORT(cellToChildren)(H3Index h, int childRes, H3Index *children) { int64_t i = 0; for (IterCellsChildren iter = iterInitParent(h, childRes); iter.h; iterStepChild(&iter)) { children[i] = iter.h; i++; } return E_SUCCESS; } /** * Zero out index digits from start to end, inclusive. * No-op if start > end. */ H3Index _zeroIndexDigits(H3Index h, int start, int end) { if (start > end) return h; H3Index m = 0; m = ~m; m <<= H3_PER_DIGIT_OFFSET * (end - start + 1); m = ~m; m <<= H3_PER_DIGIT_OFFSET * (MAX_H3_RES - end); m = ~m; return h & m; } /** * cellToCenterChild produces the center child index for a given H3 index at * the specified resolution * * @param h H3Index to find center child of * @param childRes The resolution to switch to * @param child H3Index of the center child * @return 0 (E_SUCCESS) on success */ H3Error H3_EXPORT(cellToCenterChild)(H3Index h, int childRes, H3Index *child) { if (!_hasChildAtRes(h, childRes)) return E_RES_DOMAIN; h = _zeroIndexDigits(h, H3_GET_RESOLUTION(h) + 1, childRes); H3_SET_RESOLUTION(h, childRes); *child = h; return E_SUCCESS; } /** * compactCells takes a set of hexagons all at the same resolution and * compresses them by pruning full child branches to the parent level. This is * also done for all parents recursively to get the minimum number of hex * addresses that perfectly cover the defined space. * @param h3Set Set of hexagons * @param compactedSet The output array of compressed hexagons (preallocated) * @param numHexes The size of the input and output arrays (possible that no * contiguous regions exist in the set at all and no compression possible) * @return an error code on bad input data */ H3Error H3_EXPORT(compactCells)(const H3Index *h3Set, H3Index *compactedSet, const int64_t numHexes) { if (numHexes == 0) { return E_SUCCESS; } int res = H3_GET_RESOLUTION(h3Set[0]); if (res == 0) { // No compaction possible, just copy the set to output for (int64_t i = 0; i < numHexes; i++) { compactedSet[i] = h3Set[i]; } return E_SUCCESS; } H3Index *remainingHexes = H3_MEMORY(malloc)(numHexes * sizeof(H3Index)); if (!remainingHexes) { return E_MEMORY_ALLOC; } memcpy(remainingHexes, h3Set, numHexes * sizeof(H3Index)); H3Index *hashSetArray = H3_MEMORY(calloc)(numHexes, sizeof(H3Index)); if (!hashSetArray) { H3_MEMORY(free)(remainingHexes); return E_MEMORY_ALLOC; } H3Index *compactedSetOffset = compactedSet; int64_t numRemainingHexes = numHexes; while (numRemainingHexes) { res = H3_GET_RESOLUTION(remainingHexes[0]); int parentRes = res - 1; // If parentRes is less than zero, we've compacted all the way up to the // base cells. Time to process the remaining cells. if (parentRes >= 0) { // Put the parents of the hexagons into the temp array // via a hashing mechanism, and use the reserved bits // to track how many times a parent is duplicated for (int64_t i = 0; i < numRemainingHexes; i++) { H3Index currIndex = remainingHexes[i]; // TODO: This case is coverable (reachable by fuzzer) if (currIndex != 0) { // If the reserved bits were set by the caller, the // algorithm below may encounter undefined behavior // because it expects to have set the reserved bits // itself. if (H3_GET_RESERVED_BITS(currIndex) != 0) { H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return E_CELL_INVALID; } H3Index parent; H3Error parentError = H3_EXPORT(cellToParent)(currIndex, parentRes, &parent); // Should never be reachable as a result of the compact // algorithm. Can happen if cellToParent errors e.g. // because of incompatible resolutions. if (parentError) { H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return parentError; } // Modulus hash the parent into the temp array int64_t loc = (int64_t)(parent % numRemainingHexes); int64_t loopCount = 0; while (hashSetArray[loc] != 0) { if (NEVER(loopCount > numRemainingHexes)) { // This case should not be possible because at // most one index is placed into hashSetArray // per numRemainingHexes. H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return E_FAILED; } H3Index tempIndex = hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; if (tempIndex == parent) { int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; int limitCount = 7; if (H3_EXPORT(isPentagon)( tempIndex & H3_RESERVED_MASK_NEGATIVE)) { limitCount--; } // One is added to count for this check to match // one being added to count later in this // function when checking for all children being // present. if (count + 1 > limitCount) { // Only possible on duplicate input H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return E_DUPLICATE_INPUT; } H3_SET_RESERVED_BITS(parent, count); hashSetArray[loc] = H3_NULL; } else { loc = (loc + 1) % numRemainingHexes; } loopCount++; } hashSetArray[loc] = parent; } } } // Determine which parent hexagons have a complete set // of children and put them in the compactableHexes array int64_t compactableCount = 0; int64_t maxCompactableCount = numRemainingHexes / 6; // Somehow all pentagons; conservative if (maxCompactableCount == 0) { memcpy(compactedSetOffset, remainingHexes, numRemainingHexes * sizeof(remainingHexes[0])); break; } H3Index *compactableHexes = H3_MEMORY(calloc)(maxCompactableCount, sizeof(H3Index)); if (!compactableHexes) { H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return E_MEMORY_ALLOC; } for (int64_t i = 0; i < numRemainingHexes; i++) { if (hashSetArray[i] == 0) continue; int count = H3_GET_RESERVED_BITS(hashSetArray[i]) + 1; // Include the deleted direction for pentagons as implicitly "there" if (H3_EXPORT(isPentagon)(hashSetArray[i] & H3_RESERVED_MASK_NEGATIVE)) { // We need this later on, no need to recalculate H3_SET_RESERVED_BITS(hashSetArray[i], count); // Increment count after setting the reserved bits, // since count is already incremented above, so it // will be the expected value for a complete hexagon. count++; } if (count == 7) { // Bingo! Full set! compactableHexes[compactableCount] = hashSetArray[i] & H3_RESERVED_MASK_NEGATIVE; compactableCount++; } } // Uncompactable hexes are immediately copied into the // output compactedSetOffset int64_t uncompactableCount = 0; for (int64_t i = 0; i < numRemainingHexes; i++) { H3Index currIndex = remainingHexes[i]; // TODO: This case is coverable (reachable by fuzzer) if (currIndex != H3_NULL) { bool isUncompactable = true; // Resolution 0 cells always uncompactable, and trying to take // the res -1 parent of a cell is invalid. if (parentRes >= 0) { H3Index parent; H3Error parentError = H3_EXPORT(cellToParent)(currIndex, parentRes, &parent); if (NEVER(parentError)) { H3_MEMORY(free)(compactableHexes); H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return parentError; } // Modulus hash the parent into the temp array // to determine if this index was included in // the compactableHexes array int64_t loc = (int64_t)(parent % numRemainingHexes); int64_t loopCount = 0; do { if (NEVER(loopCount > numRemainingHexes)) { // This case should not be possible because at most // one index is placed into hashSetArray per input // hexagon. H3_MEMORY(free)(compactableHexes); H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return E_FAILED; } H3Index tempIndex = hashSetArray[loc] & H3_RESERVED_MASK_NEGATIVE; if (tempIndex == parent) { int count = H3_GET_RESERVED_BITS(hashSetArray[loc]) + 1; if (count == 7) { isUncompactable = false; } break; } else { loc = (loc + 1) % numRemainingHexes; } loopCount++; } while (hashSetArray[loc] != parent); } if (isUncompactable) { compactedSetOffset[uncompactableCount] = remainingHexes[i]; uncompactableCount++; } } } // Set up for the next loop memset(hashSetArray, 0, numHexes * sizeof(H3Index)); compactedSetOffset += uncompactableCount; memcpy(remainingHexes, compactableHexes, compactableCount * sizeof(H3Index)); numRemainingHexes = compactableCount; H3_MEMORY(free)(compactableHexes); } H3_MEMORY(free)(remainingHexes); H3_MEMORY(free)(hashSetArray); return E_SUCCESS; } /** * uncompactCells takes a compressed set of cells and expands back to the * original set of cells. * * Skips elements that are H3_NULL (i.e., 0). * * @param compactedSet Set of compacted cells * @param numCompacted The number of cells in the input compacted set * @param outSet Output array for decompressed cells (preallocated) * @param numOut The size of the output array to bound check against * @param res The H3 resolution to decompress to * @return An error code if output array is too small or any cell * is smaller than the output resolution. */ H3Error H3_EXPORT(uncompactCells)(const H3Index *compactedSet, const int64_t numCompacted, H3Index *outSet, const int64_t numOut, const int res) { int64_t i = 0; for (int64_t j = 0; j < numCompacted; j++) { if (!_hasChildAtRes(compactedSet[j], res)) return E_RES_MISMATCH; for (IterCellsChildren iter = iterInitParent(compactedSet[j], res); iter.h; i++, iterStepChild(&iter)) { if (i >= numOut) return E_MEMORY_BOUNDS; // went too far; abort! outSet[i] = iter.h; } } return E_SUCCESS; } /** * uncompactCellsSize takes a compacted set of hexagons and provides * the exact size of the uncompacted set of hexagons. * * @param compactedSet Set of hexagons * @param numCompacted The number of hexes in the input set * @param res The hexagon resolution to decompress to * @param out The number of hexagons to allocate memory for * @returns E_SUCCESS on success, or another value on error */ H3Error H3_EXPORT(uncompactCellsSize)(const H3Index *compactedSet, const int64_t numCompacted, const int res, int64_t *out) { int64_t numOut = 0; for (int64_t i = 0; i < numCompacted; i++) { if (compactedSet[i] == H3_NULL) continue; int64_t childrenSize; H3Error childrenError = H3_EXPORT(cellToChildrenSize)(compactedSet[i], res, &childrenSize); if (childrenError) { // The parent res does not contain `res`. return E_RES_MISMATCH; } numOut += childrenSize; } *out = numOut; return E_SUCCESS; } /** * isResClassIII takes a hexagon ID and determines if it is in a * Class III resolution (rotated versus the icosahedron and subject * to shape distortion adding extra points on icosahedron edges, making * them not true hexagons). * @param h The H3Index to check. * @return Returns 1 if the hexagon is class III, otherwise 0. */ int H3_EXPORT(isResClassIII)(H3Index h) { return H3_GET_RESOLUTION(h) % 2; } /** * isPentagon takes an H3Index and determines if it is actually a pentagon. * @param h The H3Index to check. * @return Returns 1 if it is a pentagon, otherwise 0. */ int H3_EXPORT(isPentagon)(H3Index h) { return _isBaseCellPentagon(H3_GET_BASE_CELL(h)) && !_h3LeadingNonZeroDigit(h); } /** * Returns the highest resolution non-zero digit in an H3Index. * @param h The H3Index. * @return The highest resolution non-zero digit in the H3Index. */ Direction _h3LeadingNonZeroDigit(H3Index h) { for (int r = 1; r <= H3_GET_RESOLUTION(h); r++) if (H3_GET_INDEX_DIGIT(h, r)) return H3_GET_INDEX_DIGIT(h, r); // if we're here it's all 0's return CENTER_DIGIT; } /** * Rotate an H3Index 60 degrees counter-clockwise about a pentagonal center. * @param h The H3Index. */ H3Index _h3RotatePent60ccw(H3Index h) { // rotate in place; skips any leading 1 digits (k-axis) int foundFirstNonZeroDigit = 0; for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { // rotate this digit H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(H3_GET_INDEX_DIGIT(h, r))); // look for the first non-zero digit so we // can adjust for deleted k-axes sequence // if necessary if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { foundFirstNonZeroDigit = 1; // adjust for deleted k-axes sequence if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) h = _h3Rotate60ccw(h); } } return h; } /** * Rotate an H3Index 60 degrees clockwise about a pentagonal center. * @param h The H3Index. */ H3Index _h3RotatePent60cw(H3Index h) { // rotate in place; skips any leading 1 digits (k-axis) int foundFirstNonZeroDigit = 0; for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { // rotate this digit H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); // look for the first non-zero digit so we // can adjust for deleted k-axes sequence // if necessary if (!foundFirstNonZeroDigit && H3_GET_INDEX_DIGIT(h, r) != 0) { foundFirstNonZeroDigit = 1; // adjust for deleted k-axes sequence if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) h = _h3Rotate60cw(h); } } return h; } /** * Rotate an H3Index 60 degrees counter-clockwise. * @param h The H3Index. */ H3Index _h3Rotate60ccw(H3Index h) { for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { Direction oldDigit = H3_GET_INDEX_DIGIT(h, r); H3_SET_INDEX_DIGIT(h, r, _rotate60ccw(oldDigit)); } return h; } /** * Rotate an H3Index 60 degrees clockwise. * @param h The H3Index. */ H3Index _h3Rotate60cw(H3Index h) { for (int r = 1, res = H3_GET_RESOLUTION(h); r <= res; r++) { H3_SET_INDEX_DIGIT(h, r, _rotate60cw(H3_GET_INDEX_DIGIT(h, r))); } return h; } /** * Convert an FaceIJK address to the corresponding H3Index. * @param fijk The FaceIJK address. * @param res The cell resolution. * @return The encoded H3Index (or H3_NULL on failure). */ H3Index _faceIjkToH3(const FaceIJK *fijk, int res) { // initialize the index H3Index h = H3_INIT; H3_SET_MODE(h, H3_CELL_MODE); H3_SET_RESOLUTION(h, res); // check for res 0/base cell if (res == 0) { if (fijk->coord.i > MAX_FACE_COORD || fijk->coord.j > MAX_FACE_COORD || fijk->coord.k > MAX_FACE_COORD) { // out of range input return H3_NULL; } H3_SET_BASE_CELL(h, _faceIjkToBaseCell(fijk)); return h; } // we need to find the correct base cell FaceIJK for this H3 index; // start with the passed in face and resolution res ijk coordinates // in that face's coordinate system FaceIJK fijkBC = *fijk; // build the H3Index from finest res up // adjust r for the fact that the res 0 base cell offsets the indexing // digits CoordIJK *ijk = &fijkBC.coord; for (int r = res - 1; r >= 0; r--) { CoordIJK lastIJK = *ijk; CoordIJK lastCenter; if (isResolutionClassIII(r + 1)) { // rotate ccw _upAp7(ijk); lastCenter = *ijk; _downAp7(&lastCenter); } else { // rotate cw _upAp7r(ijk); lastCenter = *ijk; _downAp7r(&lastCenter); } CoordIJK diff; _ijkSub(&lastIJK, &lastCenter, &diff); _ijkNormalize(&diff); H3_SET_INDEX_DIGIT(h, r + 1, _unitIjkToDigit(&diff)); } // fijkBC should now hold the IJK of the base cell in the // coordinate system of the current face if (fijkBC.coord.i > MAX_FACE_COORD || fijkBC.coord.j > MAX_FACE_COORD || fijkBC.coord.k > MAX_FACE_COORD) { // out of range input return H3_NULL; } // lookup the correct base cell int baseCell = _faceIjkToBaseCell(&fijkBC); H3_SET_BASE_CELL(h, baseCell); // rotate if necessary to get canonical base cell orientation // for this base cell int numRots = _faceIjkToBaseCellCCWrot60(&fijkBC); if (_isBaseCellPentagon(baseCell)) { // force rotation out of missing k-axes sub-sequence if (_h3LeadingNonZeroDigit(h) == K_AXES_DIGIT) { // check for a cw/ccw offset face; default is ccw if (_baseCellIsCwOffset(baseCell, fijkBC.face)) { h = _h3Rotate60cw(h); } else { h = _h3Rotate60ccw(h); } } for (int i = 0; i < numRots; i++) h = _h3RotatePent60ccw(h); } else { for (int i = 0; i < numRots; i++) { h = _h3Rotate60ccw(h); } } return h; } /** * Encodes a coordinate on the sphere to the H3 index of the containing cell at * the specified resolution. * * Returns 0 on invalid input. * * @param g The spherical coordinates to encode. * @param res The desired H3 resolution for the encoding. * @param out The encoded H3Index. * @returns E_SUCCESS (0) on success, another value otherwise */ H3Error H3_EXPORT(latLngToCell)(const LatLng *g, int res, H3Index *out) { if (res < 0 || res > MAX_H3_RES) { return E_RES_DOMAIN; } if (!isfinite(g->lat) || !isfinite(g->lng)) { return E_LATLNG_DOMAIN; } FaceIJK fijk; _geoToFaceIjk(g, res, &fijk); *out = _faceIjkToH3(&fijk, res); if (ALWAYS(*out)) { return E_SUCCESS; } else { return E_FAILED; } } /** * Convert an H3Index to the FaceIJK address on a specified icosahedral face. * @param h The H3Index. * @param fijk The FaceIJK address, initialized with the desired face * and normalized base cell coordinates. * @return Returns 1 if the possibility of overage exists, otherwise 0. */ int _h3ToFaceIjkWithInitializedFijk(H3Index h, FaceIJK *fijk) { CoordIJK *ijk = &fijk->coord; int res = H3_GET_RESOLUTION(h); // center base cell hierarchy is entirely on this face int possibleOverage = 1; if (!_isBaseCellPentagon(H3_GET_BASE_CELL(h)) && (res == 0 || (fijk->coord.i == 0 && fijk->coord.j == 0 && fijk->coord.k == 0))) possibleOverage = 0; for (int r = 1; r <= res; r++) { if (isResolutionClassIII(r)) { // Class III == rotate ccw _downAp7(ijk); } else { // Class II == rotate cw _downAp7r(ijk); } _neighbor(ijk, H3_GET_INDEX_DIGIT(h, r)); } return possibleOverage; } /** * Convert an H3Index to a FaceIJK address. * @param h The H3Index. * @param fijk The corresponding FaceIJK address. */ H3Error _h3ToFaceIjk(H3Index h, FaceIJK *fijk) { int baseCell = H3_GET_BASE_CELL(h); if (NEVER(baseCell < 0) || baseCell >= NUM_BASE_CELLS) { // Base cells less than zero can not be represented in an index // To prevent reading uninitialized memory, we zero the output. fijk->face = 0; fijk->coord.i = fijk->coord.j = fijk->coord.k = 0; return E_CELL_INVALID; } // adjust for the pentagonal missing sequence; all of sub-sequence 5 needs // to be adjusted (and some of sub-sequence 4 below) if (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 5) h = _h3Rotate60cw(h); // start with the "home" face and ijk+ coordinates for the base cell of c *fijk = baseCellData[baseCell].homeFijk; if (!_h3ToFaceIjkWithInitializedFijk(h, fijk)) return E_SUCCESS; // no overage is possible; h lies on this face // if we're here we have the potential for an "overage"; i.e., it is // possible that c lies on an adjacent face CoordIJK origIJK = fijk->coord; // if we're in Class III, drop into the next finer Class II grid int res = H3_GET_RESOLUTION(h); if (isResolutionClassIII(res)) { // Class III _downAp7r(&fijk->coord); res++; } // adjust for overage if needed // a pentagon base cell with a leading 4 digit requires special handling int pentLeading4 = (_isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == 4); if (_adjustOverageClassII(fijk, res, pentLeading4, 0) != NO_OVERAGE) { // if the base cell is a pentagon we have the potential for secondary // overages if (_isBaseCellPentagon(baseCell)) { while (_adjustOverageClassII(fijk, res, 0, 0) != NO_OVERAGE) continue; } if (res != H3_GET_RESOLUTION(h)) _upAp7r(&fijk->coord); } else if (res != H3_GET_RESOLUTION(h)) { fijk->coord = origIJK; } return E_SUCCESS; } /** * Determines the spherical coordinates of the center point of an H3 index. * * @param h3 The H3 index. * @param g The spherical coordinates of the H3 cell center. */ H3Error H3_EXPORT(cellToLatLng)(H3Index h3, LatLng *g) { FaceIJK fijk; H3Error e = _h3ToFaceIjk(h3, &fijk); if (e) { return e; } _faceIjkToGeo(&fijk, H3_GET_RESOLUTION(h3), g); return E_SUCCESS; } /** * Determines the cell boundary in spherical coordinates for an H3 index. * * @param h3 The H3 index. * @param cb The boundary of the H3 cell in spherical coordinates. */ H3Error H3_EXPORT(cellToBoundary)(H3Index h3, CellBoundary *cb) { FaceIJK fijk; H3Error e = _h3ToFaceIjk(h3, &fijk); if (e) { return e; } if (H3_EXPORT(isPentagon)(h3)) { _faceIjkPentToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_PENT_VERTS, cb); } else { _faceIjkToCellBoundary(&fijk, H3_GET_RESOLUTION(h3), 0, NUM_HEX_VERTS, cb); } return E_SUCCESS; } /** * Returns the max number of possible icosahedron faces an H3 index * may intersect. * * @return int count of faces */ H3Error H3_EXPORT(maxFaceCount)(H3Index h3, int *out) { // a pentagon always intersects 5 faces, a hexagon never intersects more // than 2 (but may only intersect 1) *out = H3_EXPORT(isPentagon)(h3) ? 5 : 2; return E_SUCCESS; } /** * Find all icosahedron faces intersected by a given H3 index, represented * as integers from 0-19. The array is sparse; since 0 is a valid value, * invalid array values are represented as -1. It is the responsibility of * the caller to filter out invalid values. * * @param h3 The H3 index * @param out Output array. Must be of size maxFaceCount(h3). */ H3Error H3_EXPORT(getIcosahedronFaces)(H3Index h3, int *out) { int res = H3_GET_RESOLUTION(h3); int isPent = H3_EXPORT(isPentagon)(h3); // We can't use the vertex-based approach here for class II pentagons, // because all their vertices are on the icosahedron edges. Their // direct child pentagons cross the same faces, so use those instead. if (isPent && !isResolutionClassIII(res)) { // Note that this would not work for res 15, but this is only run on // Class II pentagons, it should never be invoked for a res 15 index. H3Index childPentagon = makeDirectChild(h3, 0); return H3_EXPORT(getIcosahedronFaces)(childPentagon, out); } // convert to FaceIJK FaceIJK fijk; H3Error err = _h3ToFaceIjk(h3, &fijk); if (err) { return err; } // Get all vertices as FaceIJK addresses. For simplicity, always // initialize the array with 6 verts, ignoring the last one for pentagons FaceIJK fijkVerts[NUM_HEX_VERTS]; int vertexCount; if (isPent) { vertexCount = NUM_PENT_VERTS; _faceIjkPentToVerts(&fijk, &res, fijkVerts); } else { vertexCount = NUM_HEX_VERTS; _faceIjkToVerts(&fijk, &res, fijkVerts); } // We may not use all of the slots in the output array, // so fill with invalid values to indicate unused slots int faceCount; H3Error maxFaceCountError = H3_EXPORT(maxFaceCount)(h3, &faceCount); if (NEVER(maxFaceCountError != E_SUCCESS)) { return maxFaceCountError; } for (int i = 0; i < faceCount; i++) { out[i] = INVALID_FACE; } // add each vertex face, using the output array as a hash set for (int i = 0; i < vertexCount; i++) { FaceIJK *vert = &fijkVerts[i]; // Adjust overage, determining whether this vertex is // on another face if (isPent) { _adjustPentVertOverage(vert, res); } else { _adjustOverageClassII(vert, res, 0, 1); } // Save the face to the output array int face = vert->face; int pos = 0; // Find the first empty output position, or the first position // matching the current face while (out[pos] != INVALID_FACE && out[pos] != face) { pos++; if (pos >= faceCount) { // Mismatch between the heuristic used in maxFaceCount and // calculation here - indicates an invalid index. return E_FAILED; } } out[pos] = face; } return E_SUCCESS; } /** * pentagonCount returns the number of pentagons (same at any resolution) * * @return int count of pentagon indexes */ int H3_EXPORT(pentagonCount)(void) { return NUM_PENTAGONS; } /** * Generates all pentagons at the specified resolution * * @param res The resolution to produce pentagons at. * @param out Output array. Must be of size pentagonCount(). */ H3Error H3_EXPORT(getPentagons)(int res, H3Index *out) { if (res < 0 || res > MAX_H3_RES) { return E_RES_DOMAIN; } int i = 0; for (int bc = 0; bc < NUM_BASE_CELLS; bc++) { if (_isBaseCellPentagon(bc)) { H3Index pentagon; setH3Index(&pentagon, res, bc, 0); out[i++] = pentagon; } } return E_SUCCESS; } /** * Returns whether or not a resolution is a Class III grid. Note that odd * resolutions are Class III and even resolutions are Class II. * @param res The H3 resolution. * @return 1 if the resolution is a Class III grid, and 0 if the resolution is * a Class II grid. */ int isResolutionClassIII(int res) { return res % 2; } /** * Validate a child position in the context of a given parent, returning * an error if validation fails. */ static H3Error validateChildPos(int64_t childPos, H3Index parent, int childRes) { int64_t maxChildCount; H3Error sizeError = H3_EXPORT(cellToChildrenSize)(parent, childRes, &maxChildCount); if (NEVER(sizeError)) { return sizeError; } if (childPos < 0 || childPos >= maxChildCount) { return E_DOMAIN; } return E_SUCCESS; } /** * Returns the position of the cell within an ordered list of all children of * the cell's parent at the specified resolution * @param child Child cell index * @param parentRes Resolution of the parent cell to find the position within * @param out Output: The position of the child cell within its parents cell * list of children */ H3Error H3_EXPORT(cellToChildPos)(H3Index child, int parentRes, int64_t *out) { int childRes = H3_GET_RESOLUTION(child); // Get the parent at res. This will catch any resolution errors H3Index originalParent; H3Error parentError = H3_EXPORT(cellToParent(child, parentRes, &originalParent)); if (parentError) { return parentError; } // Define the initial parent. Note that these variables are reassigned // within the loop. H3Index parent = originalParent; int parentIsPentagon = H3_EXPORT(isPentagon)(parent); // Walk up the resolution digits, incrementing the index *out = 0; if (parentIsPentagon) { // Pentagon logic. Pentagon parents skip the 1 digit, so the offsets are // different from hexagons for (int res = childRes; res > parentRes; res--) { H3Error parentError = H3_EXPORT(cellToParent(child, res - 1, &parent)); if (NEVER(parentError)) { return parentError; } parentIsPentagon = H3_EXPORT(isPentagon)(parent); int rawDigit = H3_GET_INDEX_DIGIT(child, res); // Validate the digit before proceeding if (rawDigit == INVALID_DIGIT || (parentIsPentagon && rawDigit == K_AXES_DIGIT)) { return E_CELL_INVALID; } int digit = parentIsPentagon && rawDigit > 0 ? rawDigit - 1 : rawDigit; if (digit != CENTER_DIGIT) { int64_t hexChildCount = _ipow(7, childRes - res); // The offset for the 0-digit slot depends on whether the // current index is the child of a pentagon. If so, the offset // is based on the count of pentagon children, otherwise, // hexagon children. *out += (parentIsPentagon ? // pentagon children. See the explanation // for getNumCells in h3api.h.in 1 + (5 * (hexChildCount - 1)) / 6 : // one hexagon's children hexChildCount) + // the other hexagon children (digit - 1) * hexChildCount; } } } else { // Hexagon logic. Offsets are simple powers of 7 for (int res = childRes; res > parentRes; res--) { int digit = H3_GET_INDEX_DIGIT(child, res); if (digit == INVALID_DIGIT) { return E_CELL_INVALID; } *out += digit * _ipow(7, childRes - res); } } if (NEVER(validateChildPos(*out, originalParent, childRes))) { // This is the result of an internal error, so return E_FAILED // instead of the validation error return E_FAILED; } return E_SUCCESS; } /** * Returns the child cell at a given position within an ordered list of all * children at the specified resolution * @param childPos Position within the ordered list * @param parent Parent cell of the cell index to find * @param childRes Resolution of the child cell index * @param child Output: child cell index */ H3Error H3_EXPORT(childPosToCell)(int64_t childPos, H3Index parent, int childRes, H3Index *child) { // Validate resolution if (childRes < 0 || childRes > MAX_H3_RES) { return E_RES_DOMAIN; } // Validate parent resolution int parentRes = H3_GET_RESOLUTION(parent); if (childRes < parentRes) { return E_RES_MISMATCH; } // Validate child pos H3Error childPosErr = validateChildPos(childPos, parent, childRes); if (childPosErr) { return childPosErr; } int resOffset = childRes - parentRes; *child = parent; int64_t idx = childPos; H3_SET_RESOLUTION(*child, childRes); if (H3_EXPORT(isPentagon)(parent)) { // Pentagon tile logic. Pentagon tiles skip the 1 digit, so the offsets // are different bool inPent = true; for (int res = 1; res <= resOffset; res++) { int64_t resWidth = _ipow(7, resOffset - res); if (inPent) { // While we are inside a parent pentagon, we need to check if // this cell is a pentagon, and if not, we need to offset its // digit to account for the skipped direction int64_t pentWidth = 1 + (5 * (resWidth - 1)) / 6; if (idx < pentWidth) { H3_SET_INDEX_DIGIT(*child, parentRes + res, 0); } else { idx -= pentWidth; inPent = false; H3_SET_INDEX_DIGIT(*child, parentRes + res, (idx / resWidth) + 2); idx %= resWidth; } } else { // We're no longer inside a pentagon, continue as for hex H3_SET_INDEX_DIGIT(*child, parentRes + res, idx / resWidth); idx %= resWidth; } } } else { // Hexagon tile logic. Offsets are simple powers of 7 for (int res = 1; res <= resOffset; res++) { int64_t resWidth = _ipow(7, resOffset - res); H3_SET_INDEX_DIGIT(*child, parentRes + res, idx / resWidth); idx %= resWidth; } } return E_SUCCESS; }