public void UnitIjkToDigit() { var zero = new CoordIjk(); var i = new CoordIjk(1, 0, 0); var outOfRange = new CoordIjk(2, 0, 0); var unNormalizedZero = new CoordIjk(2, 2, 2); Assert.AreEqual(zero.ToDirection(), Direction.CENTER_DIGIT); Assert.AreEqual(i.ToDirection(), Direction.I_AXES_DIGIT); Assert.AreEqual(outOfRange.ToDirection(), Direction.INVALID_DIGIT); Assert.AreEqual(unNormalizedZero.ToDirection(), Direction.CENTER_DIGIT); }
/// <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> /// <returns>0 on success, or another value on failure</returns> /// <!-- /// localij.c /// int localIjkToH3 /// --> public static (int, H3Index) LocalIjkToH3(this CoordIjk ijk, H3Index origin) { int res = origin.Resolution; int originBaseCell = origin.BaseCell; bool originOnPent = originBaseCell.IsBaseCellPentagon(); // This logic is very similar to faceIjkToH3 // initialize the index var outH3 = new H3Index(Constants.H3Index.H3_INIT); outH3 = outH3.SetMode(H3Mode.Hexagon).SetResolution(res); // check for res 0/base cell if (res == 0) { if (ijk.I > 1 || ijk.J > 1 || ijk.K > 1) { return(1, outH3); } Direction dir = ijk.ToDirection(); int newBaseCell = originBaseCell.GetNeighbor(dir); if (newBaseCell == Constants.BaseCells.InvalidBaseCell) { // Moving in an invalid direction off a pentagon. return(1, new H3Index()); } return(0, outH3.SetBaseCell(newBaseCell)); } // 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 var ijkCopy = new CoordIjk(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--) { var lastIJK = new CoordIjk(ijkCopy); CoordIjk lastCenter; if ((r + 1).IsResClassIii()) { // rotate ccw ijkCopy = ijkCopy.UpAp7(); lastCenter = new CoordIjk(ijkCopy); lastCenter = lastCenter.DownAp7(); } else { // rotate cw ijkCopy = ijkCopy.UpAp7R(); lastCenter = new CoordIjk(ijkCopy); lastCenter = lastCenter.DownAp7R(); } var diff = (lastIJK - lastCenter).Normalized(); outH3 = outH3.SetIndexDigit(r + 1, (ulong)diff.ToDirection()); } // 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, new H3Index()); } // lookup the correct base cell var dir2 = ijkCopy.ToDirection(); int baseCell = originBaseCell.GetNeighbor(dir2); // 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 == Constants.BaseCells.InvalidBaseCell ? 0 : baseCell.IsBaseCellPentagon() ? 1 : 0; if (dir2 != Direction.CENTER_DIGIT) { // If the index is in a warped direction, we need to un-warp the base // cell direction. There may be further need to rotate the index digits. var pentagonRotations = 0; if (originOnPent) { Direction originLeadingDigit = origin.LeadingNonZeroDigit; pentagonRotations = Constants.LocalIJ.PENTAGON_ROTATIONS_REVERSE[(int)originLeadingDigit, (int)dir2]; for (var i = 0; i < pentagonRotations; i++) { dir2 = dir2.Rotate60CounterClockwise(); } // 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 (dir2 == Direction.K_AXES_DIGIT) { return(3, new H3Index()); } baseCell = Constants.BaseCells.BaseCellNeighbors[originBaseCell, (int)dir2]; // indexOnPent does not need to be checked again since no pentagon // base cells border each other. if (baseCell == Constants.BaseCells.InvalidBaseCell) { throw new Exception("baseCell != INVALID_BASE_CELL"); } if (baseCell.IsBaseCellPentagon()) { throw new Exception("!_isBaseCellPentagon(baseCell)"); } } // Now we can determine the relation between the origin and target base // cell. int baseCellRotations = Constants.BaseCells.BaseCellNeighbor60CounterClockwiseRotation[originBaseCell, (int)dir2]; 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) { var revDir = baseCell.GetBaseCellDirection(originBaseCell); if (revDir == Direction.INVALID_DIGIT) { throw new Exception("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 (var i = 0; i < baseCellRotations; i++) { outH3 = outH3.Rotate60CounterClockwise(); } var indexLeadingDigit = outH3.LeadingNonZeroDigit; pentagonRotations = baseCell.IsBaseCellPolarPentagon() ? Constants.LocalIJ.PENTAGON_ROTATIONS_REVERSE_POLAR[(int)revDir, (int)indexLeadingDigit] : Constants.LocalIJ.PENTAGON_ROTATIONS_REVERSE_NONPOLAR[(int)revDir, (int)indexLeadingDigit]; if (pentagonRotations < 0) { throw new Exception("pentagonRotations >= 0"); } for (int i = 0; i < pentagonRotations; i++) { outH3 = outH3.RotatePent60CounterClockwise(); } } else { if (pentagonRotations < 0) { throw new Exception("pentagonRotations >= 0"); } for (var i = 0; i < pentagonRotations; i++) { outH3 = outH3.Rotate60CounterClockwise(); } // Adjust for the different coordinate space in the two base cells. for (var i = 0; i < baseCellRotations; i++) { outH3 = outH3.Rotate60CounterClockwise(); } } } else if (originOnPent && indexOnPent != 0) { var originLeadingDigit = (int)origin.LeadingNonZeroDigit; var indexLeadingDigit = (int)outH3.LeadingNonZeroDigit; int withinPentagonRotations = Constants.LocalIJ.PENTAGON_ROTATIONS_REVERSE[originLeadingDigit, indexLeadingDigit]; if (withinPentagonRotations < 0) { throw new Exception("withinPentagonRotations >= 0"); } for (var i = 0; i < withinPentagonRotations; i++) { outH3 = outH3.Rotate60CounterClockwise(); } } if (indexOnPent == 0) { return(0, outH3.SetBaseCell(baseCell)); } // TODO: There are cases in h3ToLocalIjk which are failed but not // accounted for here - instead just fail if the recovered index is // invalid. return(outH3.LeadingNonZeroDigit == Direction.K_AXES_DIGIT ? (4, new H3Index()) : (0, outH3.SetBaseCell(baseCell))); }