in src/h3lib/lib/localij.c [280:451]
int localIjkToH3(H3Index origin, const CoordIJK* ijk, H3Index* out) {
int res = H3_GET_RESOLUTION(origin);
int originBaseCell = H3_GET_BASE_CELL(origin);
int originOnPent = _isBaseCellPentagon(originBaseCell);
// This logic is very similar to faceIjkToH3
// initialize the index
*out = H3_INIT;
H3_SET_MODE(*out, H3_HEXAGON_MODE);
H3_SET_RESOLUTION(*out, res);
// check for res 0/base cell
if (res == 0) {
if (ijk->i > 1 || ijk->j > 1 || ijk->k > 1) {
// out of range input
return 1;
}
const Direction dir = _unitIjkToDigit(ijk);
const int newBaseCell = _getBaseCellNeighbor(originBaseCell, dir);
if (newBaseCell == INVALID_BASE_CELL) {
// Moving in an invalid direction off a pentagon.
return 1;
}
H3_SET_BASE_CELL(*out, newBaseCell);
return 0;
}
// we need to find the correct base cell offset (if any) for this H3 index;
// start with the passed in base cell and resolution res ijk coordinates
// in that base cell's coordinate system
CoordIJK ijkCopy = *ijk;
// build the H3Index from finest res up
// adjust r for the fact that the res 0 base cell offsets the indexing
// digits
for (int r = res - 1; r >= 0; r--) {
CoordIJK lastIJK = ijkCopy;
CoordIJK lastCenter;
if (isResClassIII(r + 1)) {
// rotate ccw
_upAp7(&ijkCopy);
lastCenter = ijkCopy;
_downAp7(&lastCenter);
} else {
// rotate cw
_upAp7r(&ijkCopy);
lastCenter = ijkCopy;
_downAp7r(&lastCenter);
}
CoordIJK diff;
_ijkSub(&lastIJK, &lastCenter, &diff);
_ijkNormalize(&diff);
H3_SET_INDEX_DIGIT(*out, r + 1, _unitIjkToDigit(&diff));
}
// ijkCopy should now hold the IJK of the base cell in the
// coordinate system of the current base cell
if (ijkCopy.i > 1 || ijkCopy.j > 1 || ijkCopy.k > 1) {
// out of range input
return 2;
}
// lookup the correct base cell
Direction dir = _unitIjkToDigit(&ijkCopy);
int baseCell = _getBaseCellNeighbor(originBaseCell, dir);
// If baseCell is invalid, it must be because the origin base cell is a
// pentagon, and because pentagon base cells do not border each other,
// baseCell must not be a pentagon.
int indexOnPent =
(baseCell == INVALID_BASE_CELL ? 0 : _isBaseCellPentagon(baseCell));
if (dir != CENTER_DIGIT) {
// If the index is in a warped direction, we need to unwarp the base
// cell direction. There may be further need to rotate the index digits.
int pentagonRotations = 0;
if (originOnPent) {
const Direction originLeadingDigit = _h3LeadingNonZeroDigit(origin);
pentagonRotations =
PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][dir];
for (int i = 0; i < pentagonRotations; i++) {
dir = _rotate60ccw(dir);
}
// The pentagon rotations are being chosen so that dir is not the
// deleted direction. If it still happens, it means we're moving
// into a deleted subsequence, so there is no index here.
if (dir == K_AXES_DIGIT) {
return 3;
}
baseCell = _getBaseCellNeighbor(originBaseCell, dir);
// indexOnPent does not need to be checked again since no pentagon
// base cells border each other.
assert(baseCell != INVALID_BASE_CELL);
assert(!_isBaseCellPentagon(baseCell));
}
// Now we can determine the relation between the origin and target base
// cell.
const int baseCellRotations =
baseCellNeighbor60CCWRots[originBaseCell][dir];
assert(baseCellRotations >= 0);
// Adjust for pentagon warping within the base cell. The base cell
// should be in the right location, so now we need to rotate the index
// back. We might not need to check for errors since we would just be
// double mapping.
if (indexOnPent) {
const Direction revDir =
_getBaseCellDirection(baseCell, originBaseCell);
assert(revDir != INVALID_DIGIT);
// Adjust for the different coordinate space in the two base cells.
// This is done first because we need to do the pentagon rotations
// based on the leading digit in the pentagon's coordinate system.
for (int i = 0; i < baseCellRotations; i++) {
*out = _h3Rotate60ccw(*out);
}
const Direction indexLeadingDigit = _h3LeadingNonZeroDigit(*out);
if (_isBaseCellPolarPentagon(baseCell)) {
pentagonRotations =
PENTAGON_ROTATIONS_REVERSE_POLAR[revDir][indexLeadingDigit];
} else {
pentagonRotations =
PENTAGON_ROTATIONS_REVERSE_NONPOLAR[revDir]
[indexLeadingDigit];
}
assert(pentagonRotations >= 0);
for (int i = 0; i < pentagonRotations; i++) {
*out = _h3RotatePent60ccw(*out);
}
} else {
assert(pentagonRotations >= 0);
for (int i = 0; i < pentagonRotations; i++) {
*out = _h3Rotate60ccw(*out);
}
// Adjust for the different coordinate space in the two base cells.
for (int i = 0; i < baseCellRotations; i++) {
*out = _h3Rotate60ccw(*out);
}
}
} else if (originOnPent && indexOnPent) {
const int originLeadingDigit = _h3LeadingNonZeroDigit(origin);
const int indexLeadingDigit = _h3LeadingNonZeroDigit(*out);
const int withinPentagonRotations =
PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][indexLeadingDigit];
assert(withinPentagonRotations >= 0);
for (int i = 0; i < withinPentagonRotations; i++) {
*out = _h3Rotate60ccw(*out);
}
}
if (indexOnPent) {
// TODO: There are cases in h3ToLocalIjk which are failed but not
// accounted for here - instead just fail if the recovered index is
// invalid.
if (_h3LeadingNonZeroDigit(*out) == K_AXES_DIGIT) {
return 4;
}
}
H3_SET_BASE_CELL(*out, baseCell);
return 0;
}