/// <summary> /// Encodes a coordinate on the sphere to the FaceIJK address of the containing /// cell at the specified resolution. /// </summary> /// <param name="res">The desired H3 resolution for the encoding.</param> /// <returns>The FaceIJK of the containing cell at resolution res.</returns> public FaceIJK ToFaceIJK(int res) { var ijk = new FaceIJK(); // first convert to hex2d var v = this.ToHex2d(res, ijk.face); // then convert to ijk+ ijk.coord = CoordIJK.FromHex2d(v); return(ijk); } // _geoToFaceIjk
public int ccwRot60; ///< number of 60 degree ccw rotations relative to primary face public FaceOrientIJK(int face, CoordIJK translate, int ccwRot60) { this.face = face; this.translate = translate; this.ccwRot60 = ccwRot60; }
/// <summary> /// Adjusts a FaceIJK address in place so that the resulting cell address is /// relative to the correct icosahedral face. /// </summary> /// <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="isSubstrate">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> public int AdjustOverageClassII(int res, bool pentLeading4, bool isSubstrate) { int overage = 0; CoordIJK ijk = coord; // get the maximum dimension value; scale if a substrate grid int maxDim = FaceIJK.maxDimByCIIres[res]; if (isSubstrate) { maxDim *= 3; } // check for overage if (isSubstrate && 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; if (ijk.k > 0) { if (ijk.j > 0) // jk "quadrant" { fijkOrient = FaceIJK.faceNeighbors[face][JK]; } else // ik "quadrant" { fijkOrient = FaceIJK.faceNeighbors[face][KI]; // adjust for the pentagonal missing sequence if (pentLeading4) { // translate origin to center of pentagon var origin = new CoordIJK(maxDim, 0, 0); var tmp = ijk - origin; // rotate to adjust for the missing sequence tmp = tmp._ijkRotate60cw(); // translate the origin back to the center of the triangle ijk = tmp + origin; } } } else // ij "quadrant" { fijkOrient = FaceIJK.faceNeighbors[face][IJ]; } face = fijkOrient.face; // rotate and translate for adjacent face for (int i = 0; i < fijkOrient.ccwRot60; i++) { ijk = ijk._ijkRotate60ccw(); } CoordIJK transVec = fijkOrient.translate; int unitScale = FaceIJK.unitScaleByCIIres[res]; if (isSubstrate) { unitScale *= 3; } transVec *= unitScale; ijk = (ijk + transVec).Normalize(); // overage points on pentagon boundaries can end up on edges if (isSubstrate && ijk.i + ijk.j + ijk.k == maxDim) // on edge { overage = 1; } } return(overage); }
/// <summary> /// Generates the cell boundary in spherical coordinates for a cell given by a /// FaceIJK address at a specified resolution. /// </summary> /// <param name="res">The H3 resolution of the cell.</param> /// <param name="isPentagon">Whether or not the cell is a pentagon.</param> /// <returns>The spherical coordinates of the cell boundary.</returns> public GeoBoundary ToGeoBoundary(int res, bool isPentagon) { if (isPentagon) { return(PentagonToGeoBoundary(res)); } var g = new GeoBoundary(); // 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 var vertsCII = new CoordIJK[NUM_HEX_VERTS] { 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 new CoordIJK(2, 0, 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 var vertsCIII = new CoordIJK[NUM_HEX_VERTS] { 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 new CoordIJK(5, 0, 1) // 5 }; // get the correct set of substrate vertices for this resolution var verts = H3Index.isResClassIII(res) ? vertsCIII : vertsCII; // adjust the center point to be in an aperture 33r substrate grid // these should be composed for speed FaceIJK centerIJK = this; centerIJK.coord = centerIJK.coord._downAp3()._downAp3r(); // 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)) { centerIJK.coord = centerIJK.coord._downAp7r(); 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. var fijkVerts = new FaceIJK[NUM_HEX_VERTS]; for (int v = 0; v < NUM_HEX_VERTS; v++) { fijkVerts[v].face = centerIJK.face; fijkVerts[v].coord = (centerIJK.coord + verts[v]).Normalize(); } // 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 < NUM_HEX_VERTS + 1; vert++) { int v = vert % NUM_HEX_VERTS; FaceIJK fijk = fijkVerts[v]; int overage = AdjustOverageClassII(adjRes, false, true); /* * 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) % NUM_HEX_VERTS; var orig2d0 = fijkVerts[lastV].coord.ToHex2d(); var orig2d1 = fijkVerts[v].coord.ToHex2d(); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; var v0 = new Vec2d(3.0 * maxDim, 0.0); var v1 = new Vec2d(-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim); var v2 = new Vec2d(-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim); int face2 = (lastFace == centerIJK.face) ? fijk.face : lastFace; Vec2d edge0; Vec2d edge1; switch (adjacentFaceDir[centerIJK.face][face2]) { case IJ: edge0 = v0; edge1 = v1; break; case JK: edge0 = v1; edge1 = v2; break; case KI: default: //assert(adjacentFaceDir[centerIJK.face][face2] == KI); edge0 = v2; edge1 = v0; break; } // find the intersection and add the lat/lon point to the result var inter = new Vec2d(0, 0); 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) { g.verts[g.numVerts] = GeoCoord.FromHex2d(inter, centerIJK.face, adjRes, true); 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 < NUM_HEX_VERTS) { var vec = fijk.coord.ToHex2d(); g.verts[g.numVerts] = GeoCoord.FromHex2d(vec, fijk.face, adjRes, true); g.numVerts++; } lastFace = fijk.face; lastOverage = overage; } return(g); }
/// <summary> /// Convert an FaceIJK address to the corresponding H3Index. /// </summary> /// <param name="res">The cell resolution.</param> /// <returns>The encoded H3Index (or 0 on failure).</returns> public H3Index ToH3Index(int res) { // initialize the index var h = new H3Index(H3Index.H3_INIT); h.Mode = H3_HEXAGON_MODE; h.Resolution = res; // check for res 0/base cell if (res == 0) { if (coord.i > MAX_FACE_COORD || coord.j > MAX_FACE_COORD || coord.k > MAX_FACE_COORD) { return(H3Index.H3_INVALID_INDEX); // out of range input } // TODO: fix h.BaseCell = this.ToBaseCell(); 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 //var fijkBC = new FaceIJK(this); var fijkBC = new FaceIJK(this); // 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; var ijk = fijkBC.coord; for (int r = res - 1; r >= 0; r--) { var lastIJK = new CoordIJK(ijk); CoordIJK lastCenter; if (H3Index.isResClassIII(r + 1)) { // rotate ccw ijk = ijk._upAp7(); lastCenter = ijk._downAp7(); } else { // rotate cw ijk = ijk._upAp7r(); lastCenter = ijk._downAp7r(); } var normalDiff = (lastIJK - lastCenter).Normalize(); h.SetIndexDigit(r + 1, normalDiff.UnitIJKToDigit()); } // 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) { return(H3Index.H3_INVALID_INDEX); // out of range input } // TODO: check, added this check for debugging since it leads to invalid negative array indexes //if (fijkBC.coord.i < 0 || fijkBC.coord.j < 0 || fijkBC.coord.k < 0) // return H3Index.H3_INVALID_INDEX; // out of range input // lookup the correct base cell var baseCell = fijkBC.ToBaseCell(); h.BaseCell = 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 ((int)h._h3LeadingNonZeroDigit() == (int)Direction.K_AXES_DIGIT) { // check for a cw/ccw offset face; default is ccw if (_baseCellIsCwOffset(baseCell, fijkBC.face)) { h = h._h3Rotate60cw(); } else { h = h._h3Rotate60ccw(); } } for (int i = 0; i < numRots; i++) { h = h._h3RotatePent60ccw(); } } else { for (int i = 0; i < numRots; i++) { h = h._h3Rotate60ccw(); } } return(h); }
public FaceIJK(FaceIJK prototype) { face = prototype.face; coord = new CoordIJK(prototype.coord.i, prototype.coord.j, prototype.coord.k); }
//public FaceIJK() : this(0, new CoordIJK(0, 0, 0)) { } public FaceIJK(int face, CoordIJK coord) { this.face = face; this.coord = coord; }
/// <summary> /// Generates the cell boundary in spherical coordinates for a pentagonal cell /// given by a FaceIJK address at a specified resolution. /// </summary> /// <param name="res">The H3 resolution of the cell.</param> /// <returns>The spherical coordinates of the cell boundary.</returns> public GeoBoundary PentagonToGeoBoundary(int res) { var g = new GeoBoundary(); // 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 var vertsCII = new CoordIJK[NUM_PENT_VERTS] { 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 var vertsCIII = new CoordIJK[NUM_PENT_VERTS] { 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 CoordIJK[] verts = H3Index.isResClassIII(res) ? vertsCIII : vertsCII; // adjust the center point to be in an aperture 33r substrate grid // these should be composed for speed FaceIJK centerIJK = this; centerIJK.coord = centerIJK.coord._downAp3()._downAp3r(); // 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)) { centerIJK.coord = centerIJK.coord._downAp7r(); 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[NUM_PENT_VERTS]; for (int v = 0; v < NUM_PENT_VERTS; v++) { fijkVerts[v].face = centerIJK.face; fijkVerts[v].coord = (centerIJK.coord + verts[v]).Normalize(); } // convert each vertex to lat/lon // adjust the face of each vertex as appropriate and introduce // edge-crossing vertices as needed g.numVerts = 0; var lastFijk = new FaceIJK(0, new CoordIJK(0, 0, 0)); for (int vert = 0; vert < NUM_PENT_VERTS + 1; vert++) { int v = vert % NUM_PENT_VERTS; FaceIJK fijk = fijkVerts[v]; var pentLeading4 = false; int overage = AdjustOverageClassII(adjRes, pentLeading4, true); if (overage == 2) // in a different triangle { while (true) { overage = AdjustOverageClassII(adjRes, pentLeading4, true); 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 = fijk; var orig2d0 = lastFijk.coord.ToHex2d(); int currentToLastDir = adjacentFaceDir[tmpFijk.face][lastFijk.face]; var fijkOrient = faceNeighbors[tmpFijk.face][currentToLastDir]; tmpFijk.face = fijkOrient.face; CoordIJK ijk = tmpFijk.coord; // rotate and translate for adjacent face for (int i = 0; i < fijkOrient.ccwRot60; i++) { ijk = ijk._ijkRotate60ccw(); } CoordIJK transVec = fijkOrient.translate; transVec *= unitScaleByCIIres[adjRes] * 3; ijk = (ijk + transVec).Normalize(); var orig2d1 = ijk.ToHex2d(); // find the appropriate icosa face edge vertexes int maxDim = maxDimByCIIres[adjRes]; var v0 = new Vec2d(3.0 * maxDim, 0.0); var v1 = new Vec2d(-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim); var v2 = new Vec2d(-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim); Vec2d edge0; Vec2d edge1; switch (adjacentFaceDir[tmpFijk.face][fijk.face]) { case IJ: edge0 = v0; edge1 = v1; break; case JK: edge0 = v1; edge1 = v2; break; case KI: default: //assert(adjacentFaceDir[tmpFijk.face][fijk.face] == KI); edge0 = v2; edge1 = v0; break; } // find the intersection and add the lat/lon point to the result var inter = new Vec2d(0, 0); Vec2d._v2dIntersect(orig2d0, orig2d1, edge0, edge1, ref inter); g.verts[g.numVerts] = GeoCoord.FromHex2d(inter, tmpFijk.face, adjRes, true); 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 < NUM_PENT_VERTS) { var vec = fijk.coord.ToHex2d(); g.verts[g.numVerts] = GeoCoord.FromHex2d(vec, fijk.face, adjRes, true); g.numVerts++; } lastFijk = fijk; } return(g); }
/// <summary> /// Transforms coordinates from the IJK+ coordinate system to the IJ coordinate system. /// </summary> /// <param name="ijk">The input IJK+ coordinates</param> public CoordIJ(CoordIJK ijk) { i = ijk.i - ijk.k; j = ijk.j - ijk.k; }