/// <summary> /// Determines the cell boundary in spherical coordinates for an H3 index. /// </summary> /// <param name="h3"> The H3 index.</param> /// <param name="gb">The boundary of the H3 cell in spherical coordinates.</param> /// <!-- Based off 3.1.1 --> public static void h3ToGeoBoundary(H3Index h3, ref GeoBoundary gb) { FaceIJK fijk = new FaceIJK(); _h3ToFaceIjk(h3, ref fijk); FaceIJK._faceIjkToGeoBoundary( ref fijk, H3_GET_RESOLUTION(h3), h3IsPentagon(h3), ref gb ); }
/// <summary> /// Whether the given coordinate has a matching vertex in the given geo boundary. /// </summary> /// <param name="vertex">Coordinate to check</param> /// <param name="boundary">Geo boundary to look in</param> /// <returns>Whether a match was found</returns> /// <!-- Based off 3.1.1 --> public static bool _hasMatchingVertex(GeoCoord vertex, GeoBoundary boundary) { for (int i = 0; i < boundary.numVerts; i++) { if (GeoCoord.geoAlmostEqualThreshold(vertex, boundary.verts[i], 0.000001)) { return(true); } } return(false); }
/// <summary> /// Returns the radius of a given hexagon in kilometers /// </summary> /// <param name="h3Index">Index of the hexagon</param> /// <returns>radius of hexagon in kilometers</returns> /// <!-- Based off 3.1.1 --> static double _hexRadiusKm(H3Index h3Index) { // There is probably a cheaper way to determine the radius of a // hexagon, but this way is conceptually simple GeoCoord h3Center = new GeoCoord(); GeoBoundary h3Boundary = new GeoBoundary(); H3Index.h3ToGeo(h3Index, ref h3Center); H3Index.h3ToGeoBoundary(h3Index, ref h3Boundary); return(GeoCoord._geoDistKm(h3Center, h3Boundary.verts)); }
public GeoBoundary(Code.GeoBoundary cgb) { VertexCount = cgb.numVerts; List <GeoCoord> lgc = new List <GeoCoord>(); foreach (var vertex in cgb.verts) { lgc.Add(new GeoCoord(vertex)); } Vertices = lgc.ToArray(); }
public static GeoBoundary GetH3UnidirectionalEdgeBoundary(Code.H3Index edge) { Code.GeoBoundary gb = new Code.GeoBoundary(); H3UniEdge.getH3UnidirectionalEdgeBoundary(edge, ref gb); var newVerts = gb.verts.Select(v => new GeoCoord(v)).ToArray(); return(new GeoBoundary { VertexCount = gb.numVerts, Vertices = newVerts }); }
/// <summary> /// Internal: Create a vertex graph from a set of hexagons. It is the /// responsibility of the caller to call destroyVertexGraph on the populated /// graph, otherwise the memory in the graph nodes will not be freed. /// </summary> /// /// <param name="h3Set">Set of hexagons</param> /// <param name="numHexes">Number of hexagons in the set</param> /// <param name="graph">Output graph</param> /// <!-- Based off 3.1.1 --> public static void h3SetToVertexGraph(ref List <H3Index> h3Set, int numHexes, ref VertexGraph graph) { GeoBoundary vertices = new GeoBoundary(); GeoCoord fromVertex = new GeoCoord(); GeoCoord toVertex = new GeoCoord(); VertexGraph.VertexNode edge; if (numHexes < 1) { // We still need to init the graph, or calls to destroyVertexGraph will // fail graph = new VertexGraph(0, 0); return; } int res = H3Index.H3_GET_RESOLUTION(h3Set[0]); const int minBuckets = 6; // TODO: Better way to calculate/guess? int numBuckets = numHexes > minBuckets ? numHexes : minBuckets; graph = new VertexGraph(numBuckets, res); // Iterate through every hexagon for (int i = 0; i < numHexes; i++) { H3Index.h3ToGeoBoundary(h3Set[i], ref vertices); // iterate through every edge for (int j = 0; j < vertices.numVerts; j++) { fromVertex = new GeoCoord(vertices.verts[j].lat, vertices.verts[j].lon); //fromVtx = vertices.verts[j]; int idx = (j + 1) % vertices.numVerts; toVertex = new GeoCoord(vertices.verts[idx].lat, vertices.verts[idx].lon); //toVtx = vertices.verts[(j + 1) % vertices.numVerts]; // If we've seen this edge already, it will be reversed edge = VertexGraph.findNodeForEdge(ref graph, toVertex, fromVertex); if (edge != null) { // If we've seen it, drop it. No edge is shared by more than 2 // hexagons, so we'll never see it again. VertexGraph.removeVertexNode(ref graph, ref edge); } else { // Add a new node for this edge VertexGraph.addVertexNode(ref graph, fromVertex, toVertex); } } } }
public static Api.GeoBoundary H3ToGeoBoundary(Code.H3Index h3) { var blank = new Code.GeoBoundary(); Code.H3Index.h3ToGeoBoundary(h3, ref blank); var newVerts = blank.verts.Select(v => new Api.GeoCoord(v)).ToArray(); return(new GeoBoundary { VertexCount = blank.numVerts, Vertices = newVerts }); }
/// <summary> /// Provides the coordinates defining the unidirectional edge. /// </summary> /// <param name="edge">The unidirectional edge H3Index</param> /// <param name="gb"> /// The geoboundary object to store the edge coordinates. /// </param> /// <!-- Based off 3.1.1 --> public static void getH3UnidirectionalEdgeBoundary(H3Index edge, ref GeoBoundary gb) { // TODO: More efficient solution :) GeoBoundary origin = new GeoBoundary(); GeoBoundary destination = new GeoBoundary(); GeoCoord postponedVertex = new GeoCoord(); bool hasPostponedVertex = false; H3Index.h3ToGeoBoundary(getOriginH3IndexFromUnidirectionalEdge(edge), ref origin); H3Index.h3ToGeoBoundary(getDestinationH3IndexFromUnidirectionalEdge(edge), ref destination); int k = 0; for (int i = 0; i < origin.numVerts; i++) { if (_hasMatchingVertex(origin.verts[i], destination)) { // If we are on vertex 0, we need to handle the case where it's the // end of the edge, not the beginning. if (i == 0 && !_hasMatchingVertex(origin.verts[i + 1], destination)) { postponedVertex = origin.verts[i]; hasPostponedVertex = true; } else { gb.verts[k] = origin.verts[i]; k++; } } } // If we postponed adding the last vertex, add it now if (hasPostponedVertex) { gb.verts[k] = postponedVertex; k++; } gb.numVerts = k; }
/// <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; } }
/// <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; } }