/// <summary> /// Rotate an H3Index 60 degrees clockwise about a pentagonal center. /// </summary> /// <param name="h"> The H3Index.</param> /// <!-- Based off 3.1.1 --> public static 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(ref h, r, (ulong)CoordIJK._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 == 0) && H3_GET_INDEX_DIGIT(h, r) != 0) { foundFirstNonZeroDigit = 1; // adjust for deleted k-axes sequence if (_h3LeadingNonZeroDigit(h) == Direction.K_AXES_DIGIT) { h = _h3Rotate60cw(ref h); } } } return(h); }
/// <summary> /// Determines the center point in spherical coordinates of a cell given by /// a FaceIJK address at a specified resolution. /// </summary> /// <param name="h">The FaceIJK address of the cell.</param> /// <param name="res">The H3 resolution of the cell.</param> /// <param name="g">The spherical coordinates of the cell center point.</param> /// <!-- Based off 3.1.1 --> public static void _faceIjkToGeo(FaceIJK h, int res, ref GeoCoord g) { Vec2d v = new Vec2d(); CoordIJK._ijkToHex2d(h.coord, ref v); _hex2dToGeo(ref v, h.face, res, 0, ref g); }
/// <summary> /// Transforms coordinates from the IJ coordinate system to the IJK+ coordinate system /// </summary> /// <param name="ij">The input IJ coordinates</param> /// <param name="ijk">The output IJK+ coordinates</param> /// <!-- Based off 3.2.0 --> public static void ijToIjk(LocalIJ.CoordIJ ij, ref CoordIJK ijk) { ijk.i = ij.i; ijk.j = ij.j; ijk.k = 0; _ijkNormalize(ref ijk); }
/// <summary> /// Find the normalized ijk coordinates of the hex in the specified digit /// direction from the specified ijk coordinates. Works in place. /// </summary> /// <param name="ijk">The ijk coordinates.</param> /// <param name="digit">The digit direction from the original ijk coordinates.</param> /// <!-- Based off 3.1.1 --> public static void _neighbor(ref CoordIJK ijk, Direction digit) { if (digit > Direction.CENTER_DIGIT && digit < Direction.NUM_DIGITS) { _ijkAdd(ijk, UNIT_VECS[(int)digit], ref ijk); _ijkNormalize(ref ijk); } }
/// <summary> /// Find the center point in 2D cartesian coordinates of a hex. /// </summary> /// <param name="h">The ijk coordinates of the hex.</param> /// <param name="v">The 2D cartesian coordinates of the hex center point.</param> /// <!-- Based off 3.1.1 --> public static void _ijkToHex2d(CoordIJK h, ref Vec2d v) { int i = h.i - h.k; int j = h.j - h.k; v.x = i - 0.5 * j; v.y = j * Constants.M_SQRT3_2; }
/// <summary> /// Rotate an H3Index 60 degrees clockwise. /// </summary> /// <param name="h">The H3Index.</param> /// <!-- Based off 3.1.1 --> public static H3Index _h3Rotate60cw(ref 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(ref h, r, (ulong)CoordIJK._rotate60cw(oldDigit)); } return(h); }
/// <summary> /// Finds the distance between the two coordinates. Returns result. /// </summary> /// <param name="c1">The first set of ijk coordinates.</param> /// <param name="c2">The second set of ijk coordinates.</param> /// <!-- Based off 3.1.1 --> public static int ijkDistance(CoordIJK c1, CoordIJK c2) { CoordIJK diff = new CoordIJK(); _ijkSub(ref c1, ref c2, ref diff); _ijkNormalize(ref diff); CoordIJK absDiff = new CoordIJK(Math.Abs(diff.i), Math.Abs(diff.j), Math.Abs(diff.k)); return(Math.Max(absDiff.i, Math.Max(absDiff.j, absDiff.k))); }
/// <summary> /// Encodes a coordinate on the sphere to the FaceIJK address of the containing /// cell at the specified resolution. /// </summary> /// <param name="g">The spherical coordinates to encode.</param> /// <param name="res">The desired H3 resolution for the encoding.</param> /// <param name="h">The FaceIJK address of the containing cell at resolution res.</param> /// <!-- Based off 3.1.1 --> public static void _geoToFaceIjk(GeoCoord g, int res, ref FaceIJK h) { // first convert to hex2d Vec2d v = new Vec2d(); _geoToHex2d(g, res, ref h.face, ref v); // then convert to ijk+ CoordIJK._hex2dToCoordIJK(ref v, ref h.coord); }
/// <summary> /// Find the normalized ijk coordinates of the indexing parent of a cell in a /// clockwise aperture 7 grid. Works in place. /// </summary> /// <param name="ijk">The ijk coordinates</param> /// <!-- Based off 3.1.1 --> public static void _upAp7r(ref CoordIJK ijk) { // convert to CoordIJ int i = ijk.i - ijk.k; int j = ijk.j - ijk.k; ijk.i = (int)Math.Round(((2 * i + j) / 7.0d), MidpointRounding.AwayFromZero); ijk.j = (int)Math.Round(((3 * j - i) / 7.0d), MidpointRounding.AwayFromZero); ijk.k = 0; _ijkNormalize(ref ijk); }
/// <summary> /// Produces an index for ij coordinates anchored by an origin. /// /// The coordinate space used by this function may have deleted /// regions or warping due to pentagonal distortion. /// /// Failure may occur if the index is too far away from the origin /// or if the index is on the other side of a pentagon. /// /// This function is experimental, and its output is not guaranteed /// to be compatible across different versions of H3. /// </summary> /// <param name="origin">An anchoring index for the ij coordinate system.</param> /// <param name="ij">ij coordinates to index.</param> /// <param name="out_h3">Index will be placed here on success.</param> /// <returns>0 on succedd, or another value on failure</returns> /// <!-- Based off 3.2.0 --> public static int experimentalLocalIjToH3(H3Index origin, CoordIJ ij, ref H3Index out_h3) { // This function is currently experimental. Once ready to be part of the // non-experimental API, this function (with the experimental prefix) will // be marked as deprecated and to be removed in the next major version. It // will be replaced with a non-prefixed function name. CoordIJK ijk = new CoordIJK(); CoordIJK.ijToIjk(ij, ref ijk); return(localIjkToH3(origin, ijk, ref out_h3)); }
/// <summary> /// Rotates ijk coordinates 60 degrees clockwise. Works in place. /// </summary> /// <param name="ijk">The ijk coordinates.</param> /// <!-- Based off 3.1.1 --> public static void _ijkRotate60cw(ref CoordIJK ijk) { // unit vector rotations CoordIJK iVec = new CoordIJK(1, 0, 1); CoordIJK jVec = new CoordIJK(1, 1, 0); CoordIJK kVec = new CoordIJK(0, 1, 1); _ijkScale(ref iVec, ijk.i); _ijkScale(ref jVec, ijk.j); _ijkScale(ref kVec, ijk.k); _ijkAdd(iVec, jVec, ref ijk); _ijkAdd(ijk, kVec, ref ijk); _ijkNormalize(ref ijk); }
/// <summary> /// Find the normalized ijk coordinates of the hex centered on the indicated /// hex at the next finer aperture 3 clockwise resolution. Works in place. /// </summary> /// <param name="ijk">The ijk coordinates.</param> /// <!-- Based off 3.1.1 --> public static void _downAp3r(ref CoordIJK ijk) { // res r unit vectors in res r+1 CoordIJK iVec = new CoordIJK(2, 1, 0); CoordIJK jVec = new CoordIJK(0, 2, 1); CoordIJK kVec = new CoordIJK(1, 0, 2); _ijkScale(ref iVec, ijk.i); _ijkScale(ref jVec, ijk.j); _ijkScale(ref kVec, ijk.k); _ijkAdd(iVec, jVec, ref ijk); _ijkAdd(ijk, kVec, ref ijk); _ijkNormalize(ref ijk); }
/// <summary> /// Determines the H3 digit corresponding to a unit vector in ijk coordinates. /// </summary> /// <param name="ijk">The ijk coordinates; must be a unit vector.</param> /// <returns>The H3 digit (0-6) corresponding to the ijk unit vector, or <see cref="Direction.INVALID_DIGIT"/> INVALID_DIGIT on failure</returns> /// <!-- Based off 3.1.1 --> public static Direction _unitIjkToDigit(ref CoordIJK ijk) { CoordIJK c = new CoordIJK(ijk.i, ijk.j, ijk.k); _ijkNormalize(ref c); Direction digit = Direction.INVALID_DIGIT; for (Direction i = Direction.CENTER_DIGIT; i < Direction.NUM_DIGITS; i++) { if (_ijkMatches(c, UNIT_VECS[(int)i]) == 1) { digit = i; break; } } return(digit); }
/// <summary> /// Produces the grid distance between the two indexes. /// /// This function may fail to find the distance between two indexes, for /// example if they are very far apart. It may also fail when finding /// distances for indexes on opposite sides of a pentagon. /// </summary> /// <param name="origin">Index to find the distance from</param> /// <param name="h3">Index to find the distance to</param> /// <returns> /// The distance, or a negative number if the library could not compute the distance /// </returns> /// <!-- Based off 3.2.0 --> public static int h3Distance(H3Index origin, H3Index h3) { CoordIJK originIjk = new CoordIJK(); CoordIJK h3Ijk = new CoordIJK(); if (h3ToLocalIjk(origin, origin, ref originIjk) != 0) { // Currently there are no tests that would cause getting the coordinates // for an index the same as the origin to fail. return(-1); // LCOV_EXCL_LINE } if (h3ToLocalIjk(origin, h3, ref h3Ijk) != 0) { return(-1); } return(CoordIJK.ijkDistance(originIjk, h3Ijk)); }
/// <summary> /// Produces ij coordinates for an index anchored by an origin. /// /// The coordinate space used by this function may have deleted /// regions or warping due to pentagonal distortion. /// /// Coordinates are only comparable if they come from the same /// origin index. /// /// Failure may occur if the index is too far away from the origin /// or if the index is on the other side of a pentagon. /// /// This function is experimental, and its output is not guaranteed /// to be compatible across different versions of H3. /// </summary> /// <param name="origin">An anchoring index for the ij coordinate system.</param> /// <param name="h3">Index to find the coordinates of</param> /// <param name="out_coord">ij coordinates of the index will be placed here on success</param> /// <returns>0 on success, or another value on failure.</returns> /// <!-- Based off 3.2.0 --> public static int experimentalH3ToLocalIj(H3Index origin, H3Index h3, CoordIJ out_coord) { // This function is currently experimental. Once ready to be part of the // non-experimental API, this function (with the experimental prefix) will // be marked as deprecated and to be removed in the next major version. It // will be replaced with a non-prefixed function name. CoordIJK ijk = new CoordIJK(); int failed = h3ToLocalIjk(origin, h3, ref ijk); if (failed != 0) { return(failed); } CoordIJK.ijkToIj(ijk, ref out_coord); return(0); }
/// <summary> /// Normalizes ijk coordinates by setting the components to the smallest possible /// values. Works in place. /// </summary> /// <param name="c">The ijk coordinates to normalize.</param> /// <!-- Based off 3.1.1 --> public static void _ijkNormalize(ref CoordIJK c) { // remove any negative values if (c.i < 0) { c.j -= c.i; c.k -= c.i; c.i = 0; } if (c.j < 0) { c.i -= c.j; c.k -= c.j; c.j = 0; } if (c.k < 0) { c.i -= c.k; c.j -= c.k; c.k = 0; } // remove the min value if needed int min = c.i; if (c.j < min) { min = c.j; } if (c.k < min) { min = c.k; } if (min > 0) { c.i -= min; c.j -= min; c.k -= min; } }
/// <summary> /// Find the normalized ijk coordinates of the hex centered on the indicated /// hex at the next finer aperture 7 clockwise resolution. Works in place. /// </summary> /// <param name="ijk">The ijk coordinates.</param> /// <!-- Based off 3.1.1 --> public static void _downAp7r(ref CoordIJK ijk) { // res r unit vectors in res r+1 CoordIJK iVec = new CoordIJK { i = 3, j = 1, k = 0 }; CoordIJK jVec = new CoordIJK { i = 0, j = 3, k = 1 }; CoordIJK kVec = new CoordIJK { i = 1, j = 0, k = 3 }; _ijkScale(ref iVec, ijk.i); _ijkScale(ref jVec, ijk.j); _ijkScale(ref kVec, ijk.k); _ijkAdd(iVec, jVec, ref ijk); _ijkAdd(ijk, kVec, ref ijk); _ijkNormalize(ref ijk); }
/// <summary> /// Convert an H3Index to the FaceIJK address on a specified icosahedral face. /// </summary> /// <param name="h"> The H3Index.</param> /// <param name="fijk"> /// The FaceIJK address, initialized with the desired face /// and normalized base cell coordinates. /// </param> /// <returns>Returns 1 if the possibility of overage exists, otherwise 0.</returns> /// <!-- Based off 3.1.1 --> internal static int _h3ToFaceIjkWithInitializedFijk(H3Index h, ref FaceIJK fijk) { CoordIJK ijk = new CoordIJK(fijk.coord.i, fijk.coord.j, fijk.coord.k); int res = H3_GET_RESOLUTION(h); // center base cell hierarchy is entirely on this face int possibleOverage = 1; if (!BaseCells._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 (isResClassIII(r)) { // Class III == rotate ccw CoordIJK._downAp7(ref ijk); } else { // Class II == rotate cw CoordIJK._downAp7r(ref ijk); } CoordIJK._neighbor(ref ijk, H3_GET_INDEX_DIGIT(h, r)); } fijk.coord.i = ijk.i; fijk.coord.j = ijk.j; fijk.coord.k = ijk.k; return(possibleOverage); }
/// <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); }
/// <summary> /// Produces an index for ijk+ coordinates anchored by an origin. /// /// The coordinate space used by this function may have deleted /// regions or warping due to pentagonal distortion. /// /// Failure may occur if the coordinates are too far away from the origin /// or if the index is on the other side of a pentagon. /// </summary> /// <param name="origin">An anchoring index for the ijk+ coordinate system.</param> /// <param name="ijk">IJK+ Coordinates to find the index of</param> /// <param name="out_h3">The index will be placed here on success</param> /// <returns>0 on success, or another value on failure</returns> /// <!-- Based off 3.2.0 --> internal static int localIjkToH3(H3Index origin, CoordIJK ijk, ref H3Index out_h3) { int res = H3Index.H3_GET_RESOLUTION(origin); int originBaseCell = H3Index.H3_GET_BASE_CELL(origin); int originOnPent = BaseCells._isBaseCellPentagon(originBaseCell) ? 1 : 0; // This logic is very similar to faceIjkToH3 // initialize the index out_h3 = H3Index.H3_INIT; H3Index.H3_SET_MODE(ref out_h3, Constants.H3_HEXAGON_MODE); H3Index.H3_SET_RESOLUTION(ref out_h3, res); Direction dir; // 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); } dir = CoordIJK._unitIjkToDigit(ref ijk); int newBaseCell = BaseCells._getBaseCellNeighbor(originBaseCell, dir); if (newBaseCell == BaseCells.INVALID_BASE_CELL) { // Moving in an invalid direction off a pentagon. return(1); } H3Index.H3_SET_BASE_CELL(ref out_h3, 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 = new CoordIJK(ijk.i, ijk.j, ijk.k); // 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 (H3Index.isResClassIII(r + 1)) { // rotate ccw CoordIJK._upAp7(ref ijkCopy); lastCenter = ijkCopy; CoordIJK._downAp7(ref lastCenter); } else { // rotate cw CoordIJK._upAp7r(ref ijkCopy); lastCenter = ijkCopy; CoordIJK._downAp7r(ref lastCenter); } CoordIJK diff = new CoordIJK(); CoordIJK._ijkSub(ref lastIJK, ref lastCenter, ref diff); CoordIJK._ijkNormalize(ref diff); H3Index.H3_SET_INDEX_DIGIT(ref out_h3, r + 1, (ulong)CoordIJK._unitIjkToDigit(ref 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 dir = CoordIJK._unitIjkToDigit(ref ijkCopy); int baseCell = BaseCells._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 == BaseCells.INVALID_BASE_CELL ? 0 : BaseCells._isBaseCellPentagon(baseCell) ? 1 : 0); if (dir != (int)Direction.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 != 0) { Direction originLeadingDigit = H3Index._h3LeadingNonZeroDigit(origin); pentagonRotations = PENTAGON_ROTATIONS_REVERSE[(int)originLeadingDigit, (int)dir]; for (int i = 0; i < pentagonRotations; i++) { dir = CoordIJK._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 == Direction.K_AXES_DIGIT) { return(3); } baseCell = BaseCells._getBaseCellNeighbor(originBaseCell, dir); // indexOnPent does not need to be checked again since no pentagon // base cells border each other. if (baseCell == BaseCells.INVALID_BASE_CELL) { throw new Exception("assert(baseCell != BaseCells.INVALID_BASE_CELL);"); } if (BaseCells._isBaseCellPolarPentagon(baseCell)) { throw new Exception("assert(!BaseCells._isBaseCellPentagon(baseCell));"); } } // Now we can determine the relation between the origin and target base // cell. int baseCellRotations = BaseCells.baseCellNeighbor60CCWRots[originBaseCell, (int)dir]; if (baseCellRotations < 0) { throw new Exception("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 != 0) { Direction revDir = BaseCells._getBaseCellDirection(baseCell, originBaseCell); if (revDir == Direction.INVALID_DIGIT) { throw new Exception("assert(revDir != Direction.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_h3 = H3Index._h3Rotate60ccw(ref out_h3); } Direction indexLeadingDigit = H3Index._h3LeadingNonZeroDigit(out_h3); if (BaseCells._isBaseCellPolarPentagon(baseCell)) { pentagonRotations = PENTAGON_ROTATIONS_REVERSE_POLAR[(int)revDir, (int)indexLeadingDigit]; } else { pentagonRotations = PENTAGON_ROTATIONS_REVERSE_NONPOLAR[(int)revDir, (int)indexLeadingDigit]; } if (pentagonRotations < 0) { throw new Exception("assert(pentagonRotations >= 0);"); } for (int i = 0; i < pentagonRotations; i++) { out_h3 = H3Index._h3RotatePent60ccw(ref out_h3); } } else { if (pentagonRotations < 0) { throw new Exception("assert(pentagonRotations >= 0);"); } for (int i = 0; i < pentagonRotations; i++) { out_h3 = H3Index._h3Rotate60ccw(ref out_h3); } // Adjust for the different coordinate space in the two base cells. for (int i = 0; i < baseCellRotations; i++) { out_h3 = H3Index._h3Rotate60ccw(ref out_h3); } } } else if (originOnPent != 0 && indexOnPent != 0) { int originLeadingDigit = (int)H3Index._h3LeadingNonZeroDigit(origin); int indexLeadingDigit = (int)H3Index._h3LeadingNonZeroDigit(out_h3); int withinPentagonRotations = PENTAGON_ROTATIONS_REVERSE[originLeadingDigit, indexLeadingDigit]; if (withinPentagonRotations < 0) { throw new Exception("assert(withinPentagonRotations >= 0);"); } for (int i = 0; i < withinPentagonRotations; i++) { out_h3 = H3Index._h3Rotate60ccw(ref out_h3); } } if (indexOnPent != 0) { // TODO: There are cases in h3ToLocalIjk which are failed but not // accounted for here - instead just fail if the recovered index is // invalid. if (H3Index._h3LeadingNonZeroDigit(out_h3) == Direction.K_AXES_DIGIT) { return(4); } } H3Index.H3_SET_BASE_CELL(ref out_h3, baseCell); return(0); }
/// <summary> /// Produces ijk+ coordinates for an index anchored by an origin. /// /// The coordinate space used by this function may have deleted /// regions or warping due to pentagonal distortion. /// /// Coordinates are only comparable if they come from the same /// origin index. /// /// Failure may occur if the index is too far away from the origin /// or if the index is on the other side of a pentagon. /// </summary> /// <param name="origin">An anchoring index for the ijk+ coordinate system</param> /// <param name="h3">Index to find the coordinates of</param> /// <param name="out_coord">ijk+ coordinates of the index will be placed here on success</param> /// <returns>0 on success, or another value on failure.</returns> /// <!-- Based off 3.2.0 --> static int h3ToLocalIjk(H3Index origin, H3Index h3, ref CoordIJK out_coord) { int res = H3Index.H3_GET_RESOLUTION(origin); if (res != H3Index.H3_GET_RESOLUTION(h3)) { return(1); } int originBaseCell = H3Index.H3_GET_BASE_CELL(origin); int baseCell = H3Index.H3_GET_BASE_CELL(h3); // Direction from origin base cell to index base cell Direction dir = 0; Direction revDir = 0; if (originBaseCell != baseCell) { dir = BaseCells._getBaseCellDirection(originBaseCell, baseCell); if (dir == Direction.INVALID_DIGIT) { // Base cells are not neighbors, can't unfold. return(2); } revDir = BaseCells._getBaseCellDirection(baseCell, originBaseCell); if (revDir == Direction.INVALID_DIGIT) { throw new Exception("assert(revDir != INVALID_DIGIT)"); } } int originOnPent = (BaseCells._isBaseCellPentagon(originBaseCell) ? 1 : 0); int indexOnPent = (BaseCells._isBaseCellPentagon(baseCell) ? 1 : 0); FaceIJK indexFijk = new FaceIJK(); if (dir != Direction.CENTER_DIGIT) { // Rotate index into the orientation of the origin base cell. // cw because we are undoing the rotation into that base cell. int baseCellRotations = BaseCells.baseCellNeighbor60CCWRots[originBaseCell, (int)dir]; if (indexOnPent != 0) { for (int i = 0; i < baseCellRotations; i++) { h3 = H3Index._h3RotatePent60cw(h3); revDir = CoordIJK._rotate60cw(revDir); if (revDir == Direction.K_AXES_DIGIT) { revDir = CoordIJK._rotate60cw(revDir); } } } else { for (int i = 0; i < baseCellRotations; i++) { h3 = H3Index._h3Rotate60cw(ref h3); revDir = CoordIJK._rotate60cw(revDir); } } } // Face is unused. This produces coordinates in base cell coordinate space. H3Index._h3ToFaceIjkWithInitializedFijk(h3, ref indexFijk); if (dir != Direction.CENTER_DIGIT) { if (baseCell == originBaseCell) { throw new Exception("assert(baseCell != originBaseCell);"); } if ((originOnPent != 0) && (indexOnPent != 0)) { throw new Exception("assert(!(originOnPent && indexOnPent));"); } int pentagonRotations = 0; int directionRotations = 0; if (originOnPent != 0) { int originLeadingDigit = (int)H3Index._h3LeadingNonZeroDigit(origin); if ((H3Index.isResClassIII(res) && FAILED_DIRECTIONS_III[originLeadingDigit, (int)dir]) || (!H3Index.isResClassIII(res) && FAILED_DIRECTIONS_II[originLeadingDigit, (int)dir])) { // TODO this part of the pentagon might not be unfolded // correctly. return(3); } directionRotations = PENTAGON_ROTATIONS[originLeadingDigit, (int)dir]; pentagonRotations = directionRotations; } else if (indexOnPent != 0) { int indexLeadingDigit = (int)H3Index._h3LeadingNonZeroDigit(h3); if ((H3Index.isResClassIII(res) && FAILED_DIRECTIONS_III[indexLeadingDigit, (int)revDir]) || (!H3Index.isResClassIII(res) && FAILED_DIRECTIONS_II[indexLeadingDigit, (int)revDir])) { // TODO this part of the pentagon might not be unfolded // correctly. return(4); } pentagonRotations = PENTAGON_ROTATIONS[(int)revDir, indexLeadingDigit]; } if (pentagonRotations < 0) { throw new Exception("assert(pentagonRotations >= 0);"); } if (directionRotations < 0) { throw new Exception("assert(directionRotations >= 0);"); } for (int i = 0; i < pentagonRotations; i++) { CoordIJK._ijkRotate60cw(ref indexFijk.coord); } CoordIJK offset = new CoordIJK(); CoordIJK._neighbor(ref offset, dir); // Scale offset based on resolution for (int r = res - 1; r >= 0; r--) { if (H3Index.isResClassIII(r + 1)) { // rotate ccw CoordIJK._downAp7(ref offset); } else { // rotate cw CoordIJK._downAp7r(ref offset); } } for (int i = 0; i < directionRotations; i++) { CoordIJK._ijkRotate60cw(ref offset); } // Perform necessary translation CoordIJK._ijkAdd(indexFijk.coord, offset, ref indexFijk.coord); CoordIJK._ijkNormalize(ref indexFijk.coord); } else if (originOnPent != 0 && indexOnPent != 0) { // If the origin and index are on pentagon, and we checked that the base // cells are the same or neighboring, then they must be the same base // cell. if (baseCell != originBaseCell) { throw new Exception("assert(baseCell == originBaseCell);"); } int originLeadingDigit = (int)H3Index._h3LeadingNonZeroDigit(origin); int indexLeadingDigit = (int)H3Index._h3LeadingNonZeroDigit(h3); if (FAILED_DIRECTIONS_III[originLeadingDigit, indexLeadingDigit] || FAILED_DIRECTIONS_II[originLeadingDigit, indexLeadingDigit]) { // TODO this part of the pentagon might not be unfolded // correctly. return(5); } int withinPentagonRotations = PENTAGON_ROTATIONS[originLeadingDigit, indexLeadingDigit]; for (int i = 0; i < withinPentagonRotations; i++) { CoordIJK._ijkRotate60cw(ref indexFijk.coord); } } out_coord = indexFijk.coord; return(0); }
/// <summary> /// Generates the cell boundary in spherical coordinates for a cell given by a /// FaceIJK address at a specified resolution. /// </summary> /// <param name="h">The FaceIJK address of the cell.</param> /// <param name="res">The H3 resolution of the cell.</param> /// <param name="isPentagon">Whether or not the cell is a pentagon.</param> /// <param name="g">The spherical coordinates of the cell boundary.</param> /// <!-- Based off 3.1.1 --> public static void _faceIjkToGeoBoundary(ref FaceIJK h, int res, int isPentagon, ref GeoBoundary g) { if (isPentagon > 0) { _faceIjkPentToGeoBoundary(ref h, res, ref g); return; } // the vertexes of an origin-centered cell in a Class II resolution on a // substrate grid with aperture sequence 33r. The aperture 3 gets us the // vertices, and the 3r gets us back to Class II. // vertices listed ccw from the i-axes CoordIJK[] vertsCII = { new CoordIJK { i = 2, j = 1, k = 0 }, // 0 new CoordIJK { i = 1, j = 2, k = 0 }, // 1 new CoordIJK { i = 0, j = 2, k = 1 }, // 2 new CoordIJK { i = 0, j = 1, k = 2 }, // 3 new CoordIJK { i = 1, j = 0, k = 2 }, // 4 new CoordIJK { i = 2, j = 0, k = 1 } // 5 }; // the vertexes of an origin-centered cell in a Class III resolution on a // substrate grid with aperture sequence 33r7r. The aperture 3 gets us the // vertices, and the 3r7r gets us to Class II. // vertices listed ccw from the i-axes CoordIJK[] vertsCIII = { new CoordIJK { i = 5, j = 4, k = 0 }, // 0 new CoordIJK { i = 1, j = 5, k = 0 }, // 1 new CoordIJK { i = 0, j = 5, k = 4 }, // 2 new CoordIJK { i = 0, j = 1, k = 5 }, // 3 new CoordIJK { i = 4, j = 0, k = 5 }, // 4 new CoordIJK { i = 5, j = 0, k = 1 } // 5 }; // get the correct set of substrate vertices for this resolution CoordIJK[] verts; if (H3Index.isResClassIII(res)) { verts = vertsCIII; } else { verts = vertsCII; } // adjust the center point to be in an aperture 33r substrate grid // these should be composed for speed FaceIJK centerIJK = new FaceIJK(h.face, new CoordIJK(h.coord.i, h.coord.j, h.coord.k)); CoordIJK._downAp3(ref centerIJK.coord); CoordIJK._downAp3r(ref centerIJK.coord); // if res is Class III we need to add a cw aperture 7 to get to // icosahedral Class II int adjRes = res; if (H3Index.isResClassIII(res)) { CoordIJK._downAp7r(ref centerIJK.coord); adjRes++; } // The center point is now in the same substrate grid as the origin // cell vertices. Add the center point substate coordinates // to each vertex to translate the vertices to that cell. FaceIJK[] fijkVerts = new FaceIJK[Constants.NUM_HEX_VERTS]; for (int v = 0; v < Constants.NUM_HEX_VERTS; v++) { fijkVerts[v] = new FaceIJK(); fijkVerts[v].face = centerIJK.face; CoordIJK._ijkAdd(centerIJK.coord, verts[v], ref fijkVerts[v].coord); CoordIJK._ijkNormalize(ref fijkVerts[v].coord); } // convert each vertex to lat/lon // adjust the face of each vertex as appropriate and introduce // edge-crossing vertices as needed g.numVerts = 0; int lastFace = -1; int lastOverage = 0; // 0: none; 1: edge; 2: overage for (int vert = 0; vert < Constants.NUM_HEX_VERTS + 1; vert++) { int v = vert % Constants.NUM_HEX_VERTS; FaceIJK fijk = new FaceIJK ( fijkVerts[v].face, new CoordIJK(fijkVerts[v].coord.i, fijkVerts[v].coord.j, fijkVerts[v].coord.k) ); int pentLeading4 = 0; int overage = _adjustOverageClassII(ref fijk, adjRes, pentLeading4, 1); /* * Check for edge-crossing. Each face of the underlying icosahedron is a * different projection plane. So if an edge of the hexagon crosses an * icosahedron edge, an additional vertex must be introduced at that * intersection point. Then each half of the cell edge can be projected * to geographic coordinates using the appropriate icosahedron face * projection. Note that Class II cell edges have vertices on the face * edge, with no edge line intersections. */ if (H3Index.isResClassIII(res) && vert > 0 && fijk.face != lastFace && lastOverage != 1) { // find hex2d of the two vertexes on original face int lastV = (v + 5) % Constants.NUM_HEX_VERTS; Vec2d orig2d0 = new Vec2d(); CoordIJK._ijkToHex2d(fijkVerts[lastV].coord, ref orig2d0); Vec2d orig2d1 = new Vec2d(); CoordIJK._ijkToHex2d(fijkVerts[v].coord, ref orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; Vec2d v0 = new Vec2d(3.0 * maxDim, 0.0); Vec2d v1 = new Vec2d(-1.5 * maxDim, 3.0 * Constants.M_SQRT3_2 * maxDim); Vec2d v2 = new Vec2d(-1.5 * maxDim, -3.0 * Constants.M_SQRT3_2 * maxDim); int face2 = lastFace == centerIJK.face ? fijk.face : lastFace; Vec2d edge0 = new Vec2d(); Vec2d edge1 = new Vec2d(); switch (adjacentFaceDir[centerIJK.face, face2]) { case IJ: edge0 = v0; edge1 = v1; break; case JK: edge0 = v1; edge1 = v2; break; case KI: default: if (adjacentFaceDir[centerIJK.face, face2] != KI) { throw new Exception("Default failure in _faceIjkToGeoBoundary"); } edge0 = v2; edge1 = v0; break; } // find the intersection and add the lat/lon point to the result Vec2d inter = new Vec2d(); Vec2d._v2dIntersect(orig2d0, orig2d1, edge0, edge1, ref inter); /* * If a point of intersection occurs at a hexagon vertex, then each * adjacent hexagon edge will lie completely on a single icosahedron * face, and no additional vertex is required. */ bool isIntersectionAtVertex = Vec2d._v2dEquals(orig2d0, inter) || Vec2d._v2dEquals(orig2d1, inter); if (!isIntersectionAtVertex) { var temp_verts = g.verts[g.numVerts]; _hex2dToGeo(ref inter, centerIJK.face, adjRes, 1, ref temp_verts); g.verts[g.numVerts] = temp_verts; g.numVerts++; Debug.WriteLine(string.Format("!IsIntersection {0}", g.numVerts)); } } // convert vertex to lat/lon and add to the result // vert == NUM_HEX_VERTS is only used to test for possible intersection // on last edge if (vert < Constants.NUM_HEX_VERTS) { Vec2d vec = new Vec2d(); CoordIJK._ijkToHex2d(fijk.coord, ref vec); var temp_verts = g.verts[g.numVerts]; _hex2dToGeo(ref vec, fijk.face, adjRes, 1, ref temp_verts); g.verts[g.numVerts] = temp_verts; g.numVerts++; } lastFace = fijk.face; lastOverage = overage; } }
public FaceIJK(int f, CoordIJK cijk) { face = f; coord = cijk; }
public FaceIJK() { face = 0; coord = new CoordIJK(0, 0, 0); }
/// <summary> /// Generates the cell boundary in spherical coordinates for a pentagonal cell /// given by a FaceIJK address at a specified resolution. /// </summary> /// <param name="h">The FaceIJK address of the pentagonal cell.</param> /// <param name="res">The H3 resolution of the cell.</param> /// <param name="g">The spherical coordinates of the cell boundary.</param> /// <!-- Based off 3.1.1 --> public static void _faceIjkPentToGeoBoundary(ref FaceIJK h, int res, ref GeoBoundary g) { // the vertexes of an origin-centered pentagon in a Class II resolution on a // substrate grid with aperture sequence 33r. The aperture 3 gets us the // vertices, and the 3r gets us back to Class II. // vertices listed ccw from the i-axes CoordIJK[] vertsCII = { new CoordIJK(2, 1, 0), // 0 new CoordIJK(1, 2, 0), // 1 new CoordIJK(0, 2, 1), // 2 new CoordIJK(0, 1, 2), // 3 new CoordIJK(1, 0, 2) // 4 }; // the vertexes of an origin-centered pentagon in a Class III resolution on // a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the // vertices, and the 3r7r gets us to Class II. vertices listed ccw from the // i-axes CoordIJK[] vertsCIII = { new CoordIJK(5, 4, 0), // 0 new CoordIJK(1, 5, 0), // 1 new CoordIJK(0, 5, 4), // 2 new CoordIJK(0, 1, 5), // 3 new CoordIJK(4, 0, 5) // 4 }; // get the correct set of substrate vertices for this resolution List <CoordIJK> verts = new List <CoordIJK>(); verts = H3Index.isResClassIII(res) ? vertsCIII.ToList() : vertsCII.ToList(); // adjust the center point to be in an aperture 33r substrate grid // these should be composed for speed FaceIJK centerIJK = new FaceIJK(); centerIJK.face = h.face; centerIJK.coord = new CoordIJK(h.coord.i, h.coord.j, h.coord.k); CoordIJK._downAp3(ref centerIJK.coord); CoordIJK._downAp3r(ref centerIJK.coord); // if res is Class III we need to add a cw aperture 7 to get to // icosahedral Class II int adjRes = res; if (H3Index.isResClassIII(res)) { CoordIJK._downAp7r(ref centerIJK.coord); adjRes++; } // The center point is now in the same substrate grid as the origin // cell vertices. Add the center point substate coordinates // to each vertex to translate the vertices to that cell. FaceIJK[] fijkVerts = new FaceIJK[Constants.NUM_PENT_VERTS]; for (int v = 0; v < Constants.NUM_PENT_VERTS; v++) { fijkVerts[v] = new FaceIJK(); fijkVerts[v].face = centerIJK.face; CoordIJK._ijkAdd(centerIJK.coord, verts[v], ref fijkVerts[v].coord); CoordIJK._ijkNormalize(ref fijkVerts[v].coord); } // convert each vertex to lat/lon // adjust the face of each vertex as appropriate and introduce // edge-crossing vertices as needed g.numVerts = 0; for (int i = 0; i < g.verts.Count; i++) { g.verts[i] = new GeoCoord(); } FaceIJK lastFijk = new FaceIJK(); for (int vert = 0; vert < Constants.NUM_PENT_VERTS + 1; vert++) { int v = vert % Constants.NUM_PENT_VERTS; FaceIJK fijk = fijkVerts[v]; int pentLeading4 = 0; int overage = _adjustOverageClassII(ref fijk, adjRes, pentLeading4, 1); if (overage == 2) // in a different triangle { while (true) { overage = _adjustOverageClassII(ref fijk, adjRes, pentLeading4, 1); if (overage != 2) // not in a different triangle { break; } } } // all Class III pentagon edges cross icosa edges // note that Class II pentagons have vertices on the edge, // not edge intersections if (H3Index.isResClassIII(res) && vert > 0) { // find hex2d of the two vertexes on the last face FaceIJK tmpFijk = new FaceIJK(fijk.face, new CoordIJK(fijk.coord.i, fijk.coord.j, fijk.coord.k)); Vec2d orig2d0 = new Vec2d(); CoordIJK._ijkToHex2d(lastFijk.coord, ref orig2d0); int currentToLastDir = adjacentFaceDir[tmpFijk.face, lastFijk.face]; FaceOrientIJK fijkOrient = new FaceOrientIJK( faceNeighbors[tmpFijk.face, currentToLastDir].face, faceNeighbors[tmpFijk.face, currentToLastDir].translate.i, faceNeighbors[tmpFijk.face, currentToLastDir].translate.j, faceNeighbors[tmpFijk.face, currentToLastDir].translate.k, faceNeighbors[tmpFijk.face, currentToLastDir].ccwRot60 ); // faceNeighbors[tmpFijk.face,currentToLastDir]; tmpFijk.face = fijkOrient.face; //CoordIJK ijk = tmpFijk.coord; CoordIJK ijk = new CoordIJK(tmpFijk.coord.i, tmpFijk.coord.j, tmpFijk.coord.k); // rotate and translate for adjacent face for (int i = 0; i < fijkOrient.ccwRot60; i++) { CoordIJK._ijkRotate60ccw(ref ijk); } CoordIJK transVec = fijkOrient.translate; CoordIJK._ijkScale(ref transVec, unitScaleByCIIres[adjRes] * 3); CoordIJK._ijkAdd(ijk, transVec, ref ijk); CoordIJK._ijkNormalize(ref ijk); Vec2d orig2d1 = new Vec2d(); CoordIJK._ijkToHex2d(ijk, ref orig2d1); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; Vec2d v0 = new Vec2d(3.0 * maxDim, 0.0); Vec2d v1 = new Vec2d(-1.5 * maxDim, 3.0 * Constants.M_SQRT3_2 * maxDim); Vec2d v2 = new Vec2d(-1.5 * maxDim, -3.0 * Constants.M_SQRT3_2 * maxDim); Vec2d edge0 = new Vec2d(); Vec2d edge1 = new Vec2d(); switch (adjacentFaceDir[tmpFijk.face, fijk.face]) { case IJ: edge0 = v0; edge1 = v1; break; case JK: edge0 = v1; edge1 = v2; break; case KI: default: if (adjacentFaceDir[tmpFijk.face, fijk.face] != KI) { throw new Exception("assert(adjacentFaceDir[tmpFijk.face][fijk.face] == KI);"); } edge0 = v2; edge1 = v0; break; } // find the intersection and add the lat/lon point to the result Vec2d inter = new Vec2d(); Vec2d._v2dIntersect(orig2d0, orig2d1, edge0, edge1, ref inter); var gnv = g.verts[g.numVerts]; _hex2dToGeo(ref inter, tmpFijk.face, adjRes, 1, ref gnv); g.verts[g.numVerts] = gnv; g.numVerts++; } // convert vertex to lat/lon and add to the result // vert == NUM_PENT_VERTS is only used to test for possible intersection // on last edge if (vert < Constants.NUM_PENT_VERTS) { Vec2d vec = new Vec2d(); CoordIJK._ijkToHex2d(fijk.coord, ref vec); var gnv = g.verts[g.numVerts]; _hex2dToGeo(ref vec, fijk.face, adjRes, 1, ref gnv); g.verts[g.numVerts] = gnv; g.numVerts++; } lastFijk = fijk; } }
public FaceOrientIJK(int f, int i, int j, int k, int c) { face = f; translate = new CoordIJK(i, j, k); ccwRot60 = c; }
/// <summary> /// Convert an H3Index to a FaceIJK address. /// </summary> /// <param name="h"> The H3Index.</param> /// <param name="fijk"> The corresponding FaceIJK address.</param> /// <!-- Based off 3.1.1 --> public static void _h3ToFaceIjk(H3Index h, ref FaceIJK fijk) { int baseCell = H3_GET_BASE_CELL(h); // adjust for the pentagonal missing sequence; all of sub-sequence 5 needs // to be adjusted (and some of sub-sequence 4 below) if (BaseCells._isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == Direction.IK_AXES_DIGIT) { h = _h3Rotate60cw(ref h); } // start with the "home" face and ijk+ coordinates for the base cell of c fijk = new FaceIJK( BaseCells.baseCellData[baseCell].homeFijk.face, new CoordIJK( BaseCells.baseCellData[baseCell].homeFijk.coord.i, BaseCells.baseCellData[baseCell].homeFijk.coord.j, BaseCells.baseCellData[baseCell].homeFijk.coord.k ) ); //fijk = BaseCells.baseCellData[baseCell].homeFijk; if (_h3ToFaceIjkWithInitializedFijk(h, ref fijk) == 0) { return; // 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 = new CoordIJK(fijk.coord.i, fijk.coord.j, fijk.coord.k); // if we're in Class III, drop into the next finer Class II grid int res = H3_GET_RESOLUTION(h); if (isResClassIII(res)) { // Class III CoordIJK._downAp7r(ref fijk.coord); res++; } // adjust for overage if needed // a pentagon base cell with a leading 4 digit requires special handling bool pentLeading4 = (BaseCells._isBaseCellPentagon(baseCell) && _h3LeadingNonZeroDigit(h) == Direction.I_AXES_DIGIT); if (FaceIJK._adjustOverageClassII(ref fijk, res, pentLeading4 ? 1 : 0, 0) > 0) { // if the base cell is a pentagon we have the potential for secondary // overages if (BaseCells._isBaseCellPentagon(baseCell)) { while (true) { if (FaceIJK._adjustOverageClassII(ref fijk, res, 0, 0) == 0) { break; } } } if (res != H3_GET_RESOLUTION(h)) { CoordIJK._upAp7r(ref fijk.coord); } } else if (res != H3_GET_RESOLUTION(h)) { fijk.coord = new CoordIJK(origIJK.i, origIJK.j, origIJK.k); } }
/// <summary> /// Adjusts a FaceIJK address in place so that the resulting cell address is /// relative to the correct icosahedral face. /// </summary> /// <param name="fijk">The FaceIJK address of the cell.</param> /// <param name="res">The H3 resolution of the cell.</param> /// <param name="pentLeading4">Whether or not the cell is a pentagon with a leading digit 4</param> /// <param name="substrate">Whether or not the cell is in a substrate grid.</param> /// <returns> /// 0 if on original face (no overage); 1 if on face edge (only occurs /// on substrate grids); 2 if overage on new face interior /// </returns> /// <!-- Based off 3.1.1 --> public static int _adjustOverageClassII(ref FaceIJK fijk, int res, int pentLeading4, int substrate) { int overage = 0; CoordIJK ijk = new CoordIJK(fijk.coord.i, fijk.coord.j, fijk.coord.k); // get the maximum dimension value; scale if a substrate grid int maxDim = maxDimByCIIres[res]; if (substrate != 0) { maxDim *= 3; } // check for overage if ((substrate != 0) && ijk.i + ijk.j + ijk.k == maxDim) // on edge { overage = 1; } else if (ijk.i + ijk.j + ijk.k > maxDim) // overage { overage = 2; FaceOrientIJK fijkOrient = new FaceOrientIJK(); if (ijk.k > 0) { if (ijk.j > 0) // jk "quadrant" { //fijkOrient = faceNeighbors[fijk.face,JK]; fijkOrient = new FaceOrientIJK( faceNeighbors[fijk.face, JK].face, faceNeighbors[fijk.face, JK].translate.i, faceNeighbors[fijk.face, JK].translate.j, faceNeighbors[fijk.face, JK].translate.k, faceNeighbors[fijk.face, JK].ccwRot60); } else // ik "quadrant" { //fijkOrient = faceNeighbors[fijk.face,KI]; fijkOrient = new FaceOrientIJK( faceNeighbors[fijk.face, KI].face, faceNeighbors[fijk.face, KI].translate.i, faceNeighbors[fijk.face, KI].translate.j, faceNeighbors[fijk.face, KI].translate.k, faceNeighbors[fijk.face, KI].ccwRot60); // adjust for the pentagonal missing sequence if (pentLeading4 != 0) { // translate origin to center of pentagon CoordIJK origin = new CoordIJK(); CoordIJK._setIJK(ref origin, maxDim, 0, 0); CoordIJK tmp = new CoordIJK(); CoordIJK._ijkSub(ref ijk, ref origin, ref tmp); // rotate to adjust for the missing sequence CoordIJK._ijkRotate60cw(ref tmp); // translate the origin back to the center of the triangle CoordIJK._ijkAdd(tmp, origin, ref ijk); } } } else // ij "quadrant" { // fijkOrient = faceNeighbors[fijk.face,IJ]; fijkOrient = new FaceOrientIJK( faceNeighbors[fijk.face, IJ].face, faceNeighbors[fijk.face, IJ].translate.i, faceNeighbors[fijk.face, IJ].translate.j, faceNeighbors[fijk.face, IJ].translate.k, faceNeighbors[fijk.face, IJ].ccwRot60); } fijk.face = fijkOrient.face; // rotate and translate for adjacent face for (int i = 0; i < fijkOrient.ccwRot60; i++) { CoordIJK._ijkRotate60ccw(ref ijk); } // CoordIJK transVec = fijkOrient.translate; CoordIJK transVec = new CoordIJK ( fijkOrient.translate.i, fijkOrient.translate.j, fijkOrient.translate.k ); int unitScale = unitScaleByCIIres[res]; if (substrate != 0) { unitScale *= 3; } CoordIJK._ijkScale(ref transVec, unitScale); CoordIJK._ijkAdd(ijk, transVec, ref ijk); CoordIJK._ijkNormalize(ref ijk); // overage points on pentagon boundaries can end up on edges if ((substrate != 0) && ijk.i + ijk.j + ijk.k == maxDim) // on edge { overage = 1; } } fijk.coord = ijk; return(overage); }
/// <summary> /// Convert an FaceIJK address to the corresponding H3Index. /// </summary> /// <param name="fijk"> The FaceIJK address.</param> /// <param name="res">The cell resolution.</param> /// <returns>The encoded H3Index (or 0 on failure).</returns> /// <!-- Based off 3.1.1 --> public static H3Index _faceIjkToH3(ref FaceIJK fijk, int res) { // initialize the index H3Index h = H3_INIT; H3_SET_MODE(ref h, Constants.H3_HEXAGON_MODE); H3_SET_RESOLUTION(ref h, res); // check for res 0/base cell if (res == 0) { if (fijk.coord.i > BaseCells.MAX_FACE_COORD || fijk.coord.j > BaseCells.MAX_FACE_COORD || fijk.coord.k > BaseCells.MAX_FACE_COORD) { // out of range input return(H3_INVALID_INDEX); } H3_SET_BASE_CELL(ref h, BaseCells._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 = new FaceIJK(fijk.face, fijk.coord); FaceIJK fijkBC = new FaceIJK(fijk.face, new CoordIJK(fijk.coord.i, fijk.coord.j, fijk.coord.k)); // 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 lastIJK = new CoordIJK(ijk.i, ijk.j, ijk.k); CoordIJK lastCenter = new CoordIJK(); if (isResClassIII(r + 1)) { // rotate ccw CoordIJK._upAp7(ref ijk); lastCenter.i = ijk.i; lastCenter.j = ijk.j; lastCenter.k = ijk.k; CoordIJK._downAp7(ref lastCenter); } else { // rotate cw CoordIJK._upAp7r(ref ijk); lastCenter.i = ijk.i; lastCenter.j = ijk.j; lastCenter.k = ijk.k; CoordIJK._downAp7r(ref lastCenter); } CoordIJK diff = new CoordIJK(); CoordIJK._ijkSub(ref lastIJK, ref lastCenter, ref diff); CoordIJK._ijkNormalize(ref diff); H3_SET_INDEX_DIGIT(ref h, r + 1, (ulong)CoordIJK._unitIjkToDigit(ref diff)); } // fijkBC should now hold the IJK of the base cell in the // coordinate system of the current face if (fijkBC.coord.i > BaseCells.MAX_FACE_COORD || fijkBC.coord.j > BaseCells.MAX_FACE_COORD || fijkBC.coord.k > BaseCells.MAX_FACE_COORD) { // out of range input return(H3_INVALID_INDEX); } // lookup the correct base cell int baseCell = BaseCells._faceIjkToBaseCell(fijkBC); H3_SET_BASE_CELL(ref h, baseCell); // rotate if necessary to get canonical base cell orientation // for this base cell int numRots = BaseCells._faceIjkToBaseCellCCWrot60(fijkBC); if (BaseCells._isBaseCellPentagon(baseCell)) { // force rotation out of missing k-axes sub-sequence if (_h3LeadingNonZeroDigit(h) == Direction.K_AXES_DIGIT) { // check for a cw/ccw offset face; default is ccw if (BaseCells._baseCellIsCwOffset(baseCell, fijkBC.face)) { h = _h3Rotate60cw(ref h); } else { h = _h3Rotate60ccw(ref h); } } for (int i = 0; i < numRots; i++) { h = _h3RotatePent60ccw(ref h); } } else { for (int i = 0; i < numRots; i++) { h = _h3Rotate60ccw(ref h); } } return(h); }
/// <summary> /// Sets an IJK coordinate to the specified component values. /// </summary> /// <param name="ijk">The IJK coordinate to set.</param> /// <param name="i">The desired i component value.</param> /// <param name="j">The desired j component value.</param> /// <param name="k">The desired k component value.</param> /// <!-- Based off 3.1.1 --> public static void _setIJK(ref CoordIJK ijk, int i, int j, int k) { ijk.i = i; ijk.j = j; ijk.k = k; }