h3_h3Index.c (689 lines of code) (raw):
/*
* Copyright 2016-2021 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"
/**
* 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.
* @return The H3 index corresponding to the string argument, or H3_NULL if
* invalid.
*/
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;
}
/**
* 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) {
if (H3_GET_HIGH_BIT(h) != 0) return 0;
if (H3_GET_MODE(h) != H3_CELL_MODE) return 0;
if (H3_GET_RESERVED_BITS(h) != 0) return 0;
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
return 0;
}
int res = H3_GET_RESOLUTION(h);
if (NEVER(res < 0 || res > MAX_H3_RES)) {
// Resolutions less than zero can not be represented in an index
return 0;
}
bool foundFirstNonZeroDigit = false;
for (int r = 1; r <= res; r++) {
Direction digit = H3_GET_INDEX_DIGIT(h, r);
if (!foundFirstNonZeroDigit && digit != CENTER_DIGIT) {
foundFirstNonZeroDigit = true;
if (_isBaseCellPentagon(baseCell) && digit == K_AXES_DIGIT) {
return 0;
}
}
if (NEVER(digit < CENTER_DIGIT) || digit >= NUM_DIGITS) return 0;
}
for (int r = res + 1; r <= MAX_H3_RES; r++) {
Direction digit = H3_GET_INDEX_DIGIT(h, r);
if (digit != INVALID_DIGIT) return 0;
}
return 1;
}
/**
* 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)
*
* @return H3Index of the parent, or H3_NULL if you actually asked for a child
*/
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
*
* @return int 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
*/
// todo: update internal implementation for int64_t
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 (int 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;
int 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 (int 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
int loc = (int)(parent % numRemainingHexes);
int 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
int compactableCount = 0;
int 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 (int 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
int uncompactableCount = 0;
for (int i = 0; i < numRemainingHexes; i++) {
H3Index currIndex = remainingHexes[i];
// TODO: This case is coverable (reachable by fuzzer)
if (currIndex != H3_NULL) {
H3Index parent;
H3Error parentError =
H3_EXPORT(cellToParent)(currIndex, parentRes, &parent);
if (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
int loc = (int)(parent % numRemainingHexes);
int loopCount = 0;
bool isUncompactable = true;
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 compactSet Set of compacted cells
* @param numCompact 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 numHexes 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)() { 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
*/
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 */
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;
}