/// <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; } }
/// <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); }