Ejemplo n.º 1
0
        /// <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
Ejemplo n.º 2
0
        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;
        }
Ejemplo n.º 3
0
        /// <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);
        }
Ejemplo n.º 4
0
        /// <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);
        }
Ejemplo n.º 5
0
        /// <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);
        }
Ejemplo n.º 6
0
 public FaceIJK(FaceIJK prototype)
 {
     face  = prototype.face;
     coord = new CoordIJK(prototype.coord.i, prototype.coord.j, prototype.coord.k);
 }
Ejemplo n.º 7
0
        //public FaceIJK() : this(0, new CoordIJK(0, 0, 0)) { }

        public FaceIJK(int face, CoordIJK coord)
        {
            this.face  = face;
            this.coord = coord;
        }
Ejemplo n.º 8
0
        /// <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);
        }
Ejemplo n.º 9
0
 /// <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;
 }