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