/// <summary> /// Returns whether or not the provided H3Indexes are neighbors. /// </summary> /// <param name="origin">The origin H3 index</param> /// <param name="destination">The destination H3 index</param> /// <returns>1 if the indexes are neighbors, 0 otherwise</returns> /// <!-- Based off 3.1.1 --> public static int h3IndexesAreNeighbors(H3Index origin, H3Index destination) { // Make sure they're hexagon indexes if (H3Index.H3_GET_MODE(ref origin) != Constants.H3_HEXAGON_MODE || H3Index.H3_GET_MODE(ref destination) != Constants.H3_HEXAGON_MODE) { return(0); } // Hexagons cannot be neighbors with themselves if (origin == destination) { return(0); } // Only hexagons in the same resolution can be neighbors if (H3Index.H3_GET_RESOLUTION(origin) != H3Index.H3_GET_RESOLUTION(destination)) { return(0); } // H3 Indexes that share the same parent are very likely to be neighbors // Child 0 is neighbor with all of its parent's 'offspring', the other // children are neighbors with 3 of the 7 children. So a simple comparison // of origin and destination parents and then a lookup table of the children // is a super-cheap way to possibly determine they are neighbors. int parentRes = H3Index.H3_GET_RESOLUTION(origin) - 1; if (parentRes > 0 && (H3Index.h3ToParent(origin, parentRes) == H3Index.h3ToParent(destination, parentRes))) { Direction originResDigit = H3Index.H3_GET_INDEX_DIGIT(origin, parentRes + 1); Direction destinationResDigit = H3Index.H3_GET_INDEX_DIGIT(destination, parentRes + 1); if (originResDigit == Direction.CENTER_DIGIT || destinationResDigit == Direction.CENTER_DIGIT) { return(1); } // These sets are the relevant neighbors in the clockwise // and counter-clockwise Direction[] neighborSetClockwise = { Direction.CENTER_DIGIT, Direction.JK_AXES_DIGIT, Direction.IJ_AXES_DIGIT, Direction.J_AXES_DIGIT, Direction.IK_AXES_DIGIT, Direction.K_AXES_DIGIT, Direction.I_AXES_DIGIT }; Direction[] neighborSetCounterclockwise = { Direction.CENTER_DIGIT, Direction.IK_AXES_DIGIT, Direction.JK_AXES_DIGIT, Direction.K_AXES_DIGIT, Direction.IJ_AXES_DIGIT, Direction.I_AXES_DIGIT, Direction.J_AXES_DIGIT }; if ( neighborSetClockwise[(int)originResDigit] == destinationResDigit || neighborSetCounterclockwise[(int)originResDigit] == destinationResDigit) { return(1); } } // Otherwise, we have to determine the neighbor relationship the "hard" way. var neighborRing = new ulong[7].Select(cell => new H3Index(cell)).ToList(); Algos.kRing(origin, 1, ref neighborRing); for (int i = 0; i < 7; i++) { if (neighborRing[i] == destination) { return(1); } } // Made it here, they definitely aren't neighbors return(0); }
/// <summary> /// Returns the hexagon index neighboring the origin, in the direction dir. /// /// Implementation note: The only reachable case where this returns 0 is if the /// origin is a pentagon and the translation is in the k direction. Thus, /// 0 can only be returned if origin is a pentagon. /// </summary> /// <param name="origin">Origin index</param> /// <param name="dir">Direction to move in</param> /// <param name="rotations"> /// Number of ccw rotations to perform to reorient the translation vector. /// Will be modified to the new number of rotations to perform (such as /// when crossing a face edge.) /// </param> /// <returns>H3Index of the specified neighbor or 0 if deleted k-subsequence distortion is encountered.</returns> /// <!-- Based off 3.2.0 --> internal static ulong h3NeighborRotations(H3Index origin, Direction dir, ref int rotations) { H3Index out_hex = origin; for (int i = 0; i < rotations; i++) { dir = CoordIJK._rotate60ccw(dir); } int newRotations = 0; int oldBaseCell = H3Index.H3_GET_BASE_CELL(out_hex); Direction oldLeadingDigit = H3Index._h3LeadingNonZeroDigit(out_hex); // Adjust the indexing digits and, if needed, the base cell. int r = H3Index.H3_GET_RESOLUTION(out_hex) - 1; while (true) { if (r == -1) { H3Index.H3_SET_BASE_CELL(ref out_hex, BaseCells.baseCellNeighbors[oldBaseCell, (int)dir]); newRotations = BaseCells.baseCellNeighbor60CCWRots[oldBaseCell, (int)dir]; if (H3Index.H3_GET_BASE_CELL(out_hex) == BaseCells.INVALID_BASE_CELL) { // Adjust for the deleted k vertex at the base cell level. // This edge actually borders a different neighbor. H3Index.H3_SET_BASE_CELL(ref out_hex, BaseCells.baseCellNeighbors[oldBaseCell, (int)Direction.IK_AXES_DIGIT]); newRotations = BaseCells.baseCellNeighbor60CCWRots[oldBaseCell, (int)Direction.IK_AXES_DIGIT]; // perform the adjustment for the k-subsequence we're skipping // over. out_hex = H3Index._h3Rotate60ccw(ref out_hex); rotations++; } break; } Direction oldDigit = H3Index.H3_GET_INDEX_DIGIT(out_hex, r + 1); Direction nextDir; if (H3Index.isResClassIII(r + 1)) { H3Index.H3_SET_INDEX_DIGIT(ref out_hex, r + 1, (ulong)NEW_DIGIT_II[(int)oldDigit, (int)dir]); nextDir = NEW_ADJUSTMENT_II[(int)oldDigit, (int)dir]; } else { H3Index.H3_SET_INDEX_DIGIT(ref out_hex, r + 1, (ulong)NEW_DIGIT_III[(int)oldDigit, (int)dir]); nextDir = NEW_ADJUSTMENT_III[(int)oldDigit, (int)dir]; } if (nextDir != Direction.CENTER_DIGIT) { dir = nextDir; r--; } else { // No more adjustment to perform break; } } int newBaseCell = H3Index.H3_GET_BASE_CELL(out_hex); if (BaseCells._isBaseCellPentagon(newBaseCell)) { int alreadyAdjustedKSubsequence = 0; // force rotation out of missing k-axes sub-sequence if (H3Index._h3LeadingNonZeroDigit(out_hex) == Direction.K_AXES_DIGIT) { if (oldBaseCell != newBaseCell) { // in this case, we traversed into the deleted // k subsequence of a pentagon base cell. // We need to rotate out of that case depending // on how we got here. // check for a cw/ccw offset face; default is ccw if (BaseCells._baseCellIsCwOffset( newBaseCell, BaseCells.baseCellData[oldBaseCell].homeFijk.face)) { out_hex = H3Index._h3Rotate60cw(ref out_hex); } else { out_hex = H3Index._h3Rotate60ccw(ref out_hex); // LCOV_EXCL_LINE } // See cwOffsetPent in testKRing.c for why this is // unreachable. alreadyAdjustedKSubsequence = 1; } else { // In this case, we traversed into the deleted // k subsequence from within the same pentagon // base cell. if (oldLeadingDigit == Direction.CENTER_DIGIT) { // Undefined: the k direction is deleted from here return(H3Index.H3_INVALID_INDEX); } switch (oldLeadingDigit) { case Direction.JK_AXES_DIGIT: // Rotate out of the deleted k subsequence // We also need an additional change to the direction we're // moving in out_hex = H3Index._h3Rotate60ccw(ref out_hex); rotations++; break; case Direction.IK_AXES_DIGIT: // Rotate out of the deleted k subsequence // We also need an additional change to the direction we're // moving in out_hex = H3Index._h3Rotate60cw(ref out_hex); rotations += 5; break; default: // Should never occur return(H3Index.H3_INVALID_INDEX); // LCOV_EXCL_LINE } } } for (int i = 0; i < newRotations; i++) { out_hex = H3Index._h3RotatePent60ccw(ref out_hex); } // Account for differing orientation of the base cells (this edge // might not follow properties of some other edges.) if (oldBaseCell != newBaseCell) { if (BaseCells._isBaseCellPolarPentagon(newBaseCell)) { // 'polar' base cells behave differently because they have all // i neighbors. if (oldBaseCell != 118 && oldBaseCell != 8 && H3Index._h3LeadingNonZeroDigit(out_hex) != Direction.JK_AXES_DIGIT) { rotations++; } } else if (H3Index._h3LeadingNonZeroDigit(out_hex) == Direction.IK_AXES_DIGIT && alreadyAdjustedKSubsequence == 0) { // account for distortion introduced to the 5 neighbor by the // deleted k subsequence. rotations++; } } } else { for (int i = 0; i < newRotations; i++) { out_hex = H3Index._h3Rotate60ccw(ref out_hex); } } rotations = (rotations + newRotations) % 6; return(out_hex); }