public void V2DIntersect() { var p0 = new Vec2d(2.0m, 2.0m); var p1 = new Vec2d(6.0m, 6.0m); var p2 = new Vec2d(0.0m, 4.0m); var p3 = new Vec2d(10.0m, 4.0m); var intersection = Vec2d.FindIntersection(p0, p1, p2, p3); const decimal expectedX = 4.0m; const decimal expectedY = 4.0m; Assert.IsTrue(Math.Abs(intersection.X - expectedX) < Constants.H3.DBL_EPSILON); Assert.IsTrue(Math.Abs(intersection.Y - expectedY) < Constants.H3.DBL_EPSILON); }
/// <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="start">The first topological vertex to return.</param> /// <param name="length">The number of topological vertexes to return.</param> /// <returns>The spherical coordinates of the cell boundary.</returns> /// <!-- /// faceijk.c /// void _faceIjkPentToGeoBoundary /// --> public static GeoBoundary PentToGeoBoundary(this FaceIjk h, int res, int start, int length) { var gb = new GeoBoundary(); int adjRes = res; var centerIjk = h; IList <FaceIjk> fijkVerts = new FaceIjk[Constants.H3.NUM_PENT_VERTS]; (_, adjRes, fijkVerts) = centerIjk.PentToVerts(adjRes, fijkVerts); // If we're returning the entire loop, we need one more iteration in case // of a distortion vertex on the last edge int additionalIteration = length == Constants.H3.NUM_PENT_VERTS ? 1 : 0; // convert each vertex to lat/lon // adjust the face of each vertex as appropriate and introduce // edge-crossing vertices as needed gb.NumVerts = 0; var lastFijk = new FaceIjk(); for (int vert = start; vert < start + length + additionalIteration; vert++) { int v = vert % Constants.H3.NUM_PENT_VERTS; var fijk = fijkVerts[v]; (_, fijk) = fijk.AdjustPentOverage(adjRes); // all Class III pentagon edges cross icosahedron edges // note that Class II pentagons have vertices on the edge, // not edge intersections if (res.IsResClassIii() && vert > start) { // find hex2d of the two vertexes on the last face var tmpFijk = fijk; var orig2d0 = lastFijk.Coord.ToHex2d(); int currentToLastDir = Constants.FaceIjk.AdjacentFaceDir[tmpFijk.Face, lastFijk.Face]; var fijkOrient = Constants.FaceIjk.FaceNeighbors[tmpFijk.Face, currentToLastDir]; tmpFijk = tmpFijk.ReplaceFace(fijkOrient.Face); // Borrow ijk var ijk = tmpFijk.Coord; // rotate and translate for adjacent face for (var i = 0; i < fijkOrient.Ccw60Rotations; i++) { ijk = ijk.Rotate60CounterClockwise(); } var transVec = fijkOrient.Translate; var scaleRes = Constants.FaceIjk.UnitScaleByCiiRes[adjRes] * 3; transVec *= scaleRes; ijk += transVec; ijk = ijk.Normalized(); var orig2d1 = ijk.ToHex2d(); // find the appropriate icosahedron face edge vertexes int maxDim = Constants.FaceIjk.MaxDimByCiiRes[adjRes]; var v0 = new Vec2d(3.0m * maxDim, 0.0m); var v1 = new Vec2d(-1.5m * maxDim, 3.0m * Constants.H3.M_SQRT3_2 * maxDim); var v2 = new Vec2d(-1.5m * maxDim, -3.0m * Constants.H3.M_SQRT3_2 * maxDim); Vec2d edge0; Vec2d edge1; switch (Constants.FaceIjk.AdjacentFaceDir[tmpFijk.Face, fijk.Face]) { case Constants.FaceIjk.IJ: edge0 = v0; // new Vec2d(v0.X, v0.Y); edge1 = v1; //new Vec2d(v1.X, v1.Y); break; case Constants.FaceIjk.JK: edge0 = v1; //new Vec2d(v1.X, v1.Y); edge1 = v2; //new Vec2d(v2.X, v2.Y); break; default: if (Constants.FaceIjk.AdjacentFaceDir[tmpFijk.Face, fijk.Face] != Constants.FaceIjk.KI) { throw new Exception("assert(adjacentFaceDir[tmpFijk.face][fijk.face] == KI);"); } edge0 = v2; //new Vec2d(v2.X, v2.Y); edge1 = v0; //new Vec2d(v0.X, v0.Y); break; } // find the intersection and add the lat/lon point to the result var inter = Vec2d.FindIntersection(orig2d0, orig2d1, edge0, edge1); gb.Verts[gb.NumVerts] = inter.ToGeoCoord(tmpFijk.Face, adjRes, 1); gb.NumVerts++; } // convert vertex to lat/lon and add to the result // vert == start + NUM_PENT_VERTS is only used to test for possible // intersection on last edge if (vert < start + Constants.H3.NUM_PENT_VERTS) { gb.Verts[gb.NumVerts] = fijk.Coord .ToHex2d() .ToGeoCoord(fijk.Face, adjRes, 1); gb.NumVerts++; } lastFijk = new FaceIjk(fijk); } return(gb); }
/// <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="start">The first topological vertex to return</param> /// <param name="length">The number of topological vertexes to return</param> /// <returns>The spherical coordinates of the cell boundary</returns> /// <!-- /// faceijk.c /// void _faceIjkToGeoBoundary /// --> public static GeoBoundary ToGeoBoundary(this FaceIjk h, int res, int start, int length) { int adjRes = res; var centerIjk = h; IList <FaceIjk> fijkVerts = new FaceIjk[Constants.H3.NUM_HEX_VERTS]; (centerIjk, adjRes, fijkVerts) = centerIjk.ToVerts(adjRes, fijkVerts); // If we're returning the entire loop, we need one more iteration in case // of a distortion vertex on the last edge int additionalIteration = length == Constants.H3.NUM_HEX_VERTS ? 1 : 0; var g = new GeoBoundary { NumVerts = 0 }; // convert each vertex to lat/lon // adjust the face of each vertex as appropriate and introduce // edge-crossing vertices as needed int lastFace = -1; var lastOverage = Overage.NO_OVERAGE; for (int vert = start; vert < start + length + additionalIteration; vert++) { int v = vert % Constants.H3.NUM_HEX_VERTS; var fijk = fijkVerts[v]; const int pentLeading4 = 0; Overage overage; (overage, fijk) = fijk.AdjustOverageClassIi(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 (res.IsResClassIii() && vert > start && fijk.Face != lastFace && lastOverage != Overage.FACE_EDGE) { // find hex2d of the two vertexes on original face int lastV = (v + 5) % Constants.H3.NUM_HEX_VERTS; var orig2d0 = fijkVerts[lastV].Coord.ToHex2d(); var orig2d1 = fijkVerts[v].Coord.ToHex2d(); // find the appropriate icosahedron face edge vertexes int maxDim = Constants.FaceIjk.MaxDimByCiiRes[adjRes]; var v0 = new Vec2d(3.0m * maxDim, 0.0m); var v1 = new Vec2d(-1.5m * maxDim, 3.0m * Constants.H3.M_SQRT3_2 * maxDim); var v2 = new Vec2d(-1.5m * maxDim, -3.0m * Constants.H3.M_SQRT3_2 * maxDim); int face2 = lastFace == centerIjk.Face ? fijk.Face : lastFace; Vec2d edge0; Vec2d edge1; switch (Constants.FaceIjk.AdjacentFaceDir[centerIjk.Face, face2]) { case Constants.FaceIjk.IJ: edge0 = v0; edge1 = v1; break; case Constants.FaceIjk.JK: edge0 = v1; edge1 = v2; break; case Constants.FaceIjk.KI: edge0 = v2; edge1 = v0; break; default: throw new Exception ( $"(adjacentFaceDir[centerIJK.face][face2] == KI) idx0: {centerIjk.Face} idx1: {face2}" ); } // find the intersection and add the lat/lon point to the result var inter = Vec2d.FindIntersection(orig2d0, orig2d1, edge0, edge1); /* * 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 = orig2d0 == inter || orig2d1 == inter; if (!isIntersectionAtVertex) { g.Verts[g.NumVerts] = inter.ToGeoCoord(centerIjk.Face, adjRes, 1); g.NumVerts++; } } // convert vertex to lat/lon and add to the result // vert == start + NUM_HEX_VERTS is only used to test for possible // intersection on last edge if (vert < start + Constants.H3.NUM_HEX_VERTS) { var vec = fijk.Coord.ToHex2d(); g.Verts[g.NumVerts] = vec.ToGeoCoord(fijk.Face, adjRes, 1); g.NumVerts++; } lastFace = fijk.Face; lastOverage = overage; } return(g); }