From ceecd065b1fb38307ca6a2b43c7a9acc04bddad2 Mon Sep 17 00:00:00 2001 From: Justin Hwang Date: Thu, 22 Oct 2026 13:18:33 -0400 Subject: [PATCH] h3: group the constants and derive the ones that repeat a value constants.go was one flat block with a few related declarations scattered through index.go, hierarchy.go and faceijk.go. It is now one block per concern: exported limits, degree spans, the C library's mathematical constants, cell geometry counts, index digits, faceNeighbors slots, the index bit layout and the table sentinels. Values that restate another are derived from it. The digits are the C Direction enum, 0 through 7 in order, so they are an iota sequence ending in numDigits, the one name for 7 as both the digit count and the unset value of a digit; invalidDigit was the same constant under another name. dirIJ, dirKI and dirJK are iota + 1, slot 0 of faceNeighbors being the face itself. twoPiDeg and halfPiDeg come from piDeg, digitMask from perDigitOffset, h3Init is every digit set to numDigits, and numCellEdges is one edge per hexagon vertex. maxCellBoundaryVerts duplicated the exported MaxCellBndryVerts and is gone. numHexChildren and numPentChildren were also 7 and 6 under other names, one child per valid digit less the pentagon's deleted one; fullChildCount states them directly, as its doc comment already did. polyfill set a parent's digit to digitMask where it meant the unset digit. No value changed. Co-Authored-By: Claude Fable 5.1 --- bbox.go | 2 +- boundary.go | 2 +- boundary_test.go | 4 +- constants.go | 184 ++++++++++++++++++++++++--------------- directededge.go | 2 +- exact_projection_test.go | 4 +- faceijk.go | 13 ++- grid.go | 10 +-- grid_test.go | 14 +-- hierarchy.go | 19 ++-- hierarchy_test.go | 2 +- index.go | 25 +----- index_test.go | 2 +- localij.go | 18 ++-- localij_test.go | 4 +- polyfill.go | 4 +- sets_test.go | 4 +- vertex.go | 16 ++-- vertex_test.go | 14 +-- 19 files changed, 179 insertions(+), 164 deletions(-) diff --git a/bbox.go b/bbox.go index 3b511e8..f89106c 100644 --- a/bbox.go +++ b/bbox.go @@ -120,7 +120,7 @@ func (c Cell) hexRadiusKm() float64 { res := c.Resolution() center := fijk.toVec3(res).toLatLng() - var buf [maxCellBoundaryVerts]LatLng + var buf [MaxCellBndryVerts]LatLng var boundary CellBoundary if c.IsPentagon() { diff --git a/boundary.go b/boundary.go index 5dd01fa..cf9f6fb 100644 --- a/boundary.go +++ b/boundary.go @@ -32,7 +32,7 @@ func CellToBoundary(c Cell) (CellBoundary, error) { // Boundary returns the geographic boundary of the cell as an ordered list of // vertices in degrees. func (c Cell) Boundary() (CellBoundary, error) { - return c.appendBoundary(make(CellBoundary, 0, maxCellBoundaryVerts)) + return c.appendBoundary(make(CellBoundary, 0, MaxCellBndryVerts)) } // appendBoundary appends the geographic boundary of the cell to out and returns diff --git a/boundary_test.go b/boundary_test.go index d06e569..8e1fe34 100644 --- a/boundary_test.go +++ b/boundary_test.go @@ -91,9 +91,9 @@ func TestCellToBoundarySweep(t *testing.T) { minVerts = numPentVerts } - if len(boundary) < minVerts || len(boundary) > maxCellBoundaryVerts { + if len(boundary) < minVerts || len(boundary) > MaxCellBndryVerts { t.Fatalf("CellToBoundary(%015x): %d vertices, want %d..%d", - uint64(c), len(boundary), minVerts, maxCellBoundaryVerts) + uint64(c), len(boundary), minVerts, MaxCellBndryVerts) } for i, vert := range boundary { diff --git a/constants.go b/constants.go index 98924b1..2b87ec0 100644 --- a/constants.go +++ b/constants.go @@ -22,7 +22,9 @@ import "math" const ( // MaxResolution is the finest H3 resolution. MaxResolution = 15 - // MaxCellBndryVerts is the maximum number of vertices in a CellBoundary. + // MaxCellBndryVerts is the most vertices a CellBoundary can have: six + // topological vertices plus up to four where Class III edges cross + // icosahedron face edges. MaxCellBndryVerts = 10 // NumBaseCells is the number of resolution-0 base cells. NumBaseCells = 122 @@ -33,94 +35,140 @@ const ( // InvalidH3Index is the zero value returned for an invalid index. InvalidH3Index = 0 // DegsToRads converts degrees to radians when multiplied. - DegsToRads = math.Pi / 180.0 + DegsToRads = math.Pi / piDeg // RadsToDegs converts radians to degrees when multiplied. - RadsToDegs = 180.0 / math.Pi + RadsToDegs = piDeg / math.Pi +) - // piDeg, twoPiDeg and halfPiDeg are angle spans in degrees, the units of - // the public API. They stand in for the H3 C library's radian M_PI, M_2PI - // and M_PI_2 when detecting and normalizing antimeridian-crossing geometry. +// Angle spans in degrees, the units of the public API. They stand in for the +// H3 C library's radian M_PI, M_2PI and M_PI_2 when detecting and normalizing +// antimeridian-crossing geometry. +const ( piDeg = 180.0 - twoPiDeg = 360.0 - halfPiDeg = 90.0 - - epsilon = 1e-16 - m2PI = 2 * math.Pi - mSqrt7 = 2.6457513110645905905016157536392604257102 - mRSqrt7 = 0.37796447300922722721451653623418006081576 - mRSin60 = 1.1547005383792515290182975610039149112953 - mSqrt3Half = 0.8660254037844386467637231707529361834714 - mOneSeventh = 1.0 / 7.0 - mOneThird = 1.0 / 3.0 - mAP7RotRads = 0.333473172251832115336090755351601070065900389 - invRes0UGnomonic = 2.61803398874989588842 - res0UGnomonic = 0.38196601125010500003 - maxFaceCoord = 2 - - // numHexVerts and numPentVerts are the topological vertex counts of a - // hexagon and a pentagon cell, respectively. - numHexVerts = 6 - numPentVerts = 5 - - // maxCellBoundaryVerts is the most vertices a cell boundary can have: six - // topological vertices plus up to four where Class III edges cross - // icosahedron face edges. - maxCellBoundaryVerts = 10 - - // maxEdgeBoundaryVerts is the most vertices a directed edge boundary can - // have: its two endpoints plus one where a Class III edge crosses an - // icosahedron face edge. - maxEdgeBoundaryVerts = 3 + twoPiDeg = 2 * piDeg + halfPiDeg = piDeg / 2 +) +// Mathematical constants from the H3 C library's constants.h, kept to the +// same digits so projections round identically. +const ( + // epsilon is the threshold below which two unit vectors are the same point. + epsilon = 1e-16 // fltEpsilon is the 32-bit float epsilon used to detect when a cell-boundary // edge intersection coincides with an existing vertex, matching the H3 C // library's use of FLT_EPSILON. fltEpsilon = 1.1920928955078125e-07 - // faceNeighbors quadrant indices: the direction from a face to the adjacent - // face that shares the corresponding pair of axes. - dirIJ = 1 - dirKI = 2 - dirJK = 3 + m2PI = 2 * math.Pi + mSqrt7 = 2.6457513110645905905016157536392604257102 + mRSqrt7 = 0.37796447300922722721451653623418006081576 + mRSin60 = 1.1547005383792515290182975610039149112953 + mSqrt3Half = 0.8660254037844386467637231707529361834714 + mOneSeventh = 1.0 / 7.0 + mOneThird = 1.0 / 3.0 + // mAP7RotRads is the rotation angle between Class II and Class III + // resolution axes, asin(sqrt(3/28)). + mAP7RotRads = 0.333473172251832115336090755351601070065900389 + + // res0UGnomonic is the scaling factor from hex2d to gnomonic coordinates + // at resolution 0; invRes0UGnomonic is its inverse. + res0UGnomonic = 0.38196601125010500003 + invRes0UGnomonic = 2.61803398874989588842 // earthRadiusKm is the authalic (equal-area) radius of the Earth in // kilometers, used to convert spherical measures to physical units. earthRadiusKm = 6371.007180918475 - // h3Init has all 15 digit slots set to 7 (invalid); mode/res/base cell are 0. - h3Init = 35184372088831 - - centerDigit = 0 - kAxesDigit = 1 - jAxesDigit = 2 - jkAxesDigit = 3 - iAxesDigit = 4 - ikAxesDigit = 5 - ijAxesDigit = 6 - invalidDigit = 7 - numDigits = 7 - - // H3 index bit-layout offsets and masks, matching h3_h3Index.h and - // h3_constants.h. - modeOffset = 59 // H3_MODE_OFFSET - resolutionOffset = 52 // H3_RES_OFFSET - baseCellOffset = 45 // H3_BC_OFFSET - perDigitOffset = 3 // H3_PER_DIGIT_OFFSET - digitMask = 7 // H3_DIGIT_MASK - resolutionMask = 0xF // low 4 bits of the resolution field - - // numCellEdges is the number of directed edges originating at a cell. - numCellEdges = 6 + // maxFaceCoord is the largest IJK coordinate on a resolution-0 face. + maxFaceCoord = 2 +) + +// Cell geometry counts. +const ( + // numHexVerts and numPentVerts are the topological vertex counts of a + // hexagon and a pentagon cell, respectively. + numHexVerts = 6 + numPentVerts = 5 + + // numCellEdges is the number of directed edges originating at a cell, one + // per hexagon vertex. + numCellEdges = numHexVerts // oneRingSize is the cell count of a grid disk of radius 1: the origin // plus one neighbor per edge. oneRingSize = numCellEdges + 1 // numEdgeCells is the number of cells a directed edge connects. numEdgeCells = 2 + + // maxEdgeBoundaryVerts is the most vertices a directed edge boundary can + // have: its two endpoints plus one where a Class III edge crosses an + // icosahedron face edge. + maxEdgeBoundaryVerts = 3 +) + +// Index digits, the H3 C library's Direction enum: the IJK axes a digit +// points along, with the K, J and I bits valued 1, 2 and 4. +const ( + centerDigit = iota // no axes + kAxesDigit // K + jAxesDigit // J + jkAxesDigit // J and K + iAxesDigit // I + ikAxesDigit // I and K + ijAxesDigit // I and J + numDigits // every valid digit is below it; also the unset value of a digit +) + +// faceNeighbors slot indices: the direction from a face to the adjacent face +// that shares the corresponding pair of axes. Slot 0 is the face itself. +const ( + dirIJ = iota + 1 + dirKI + dirJK +) + +// H3 index bit layout, matching h3Index.h: from the high bit down, one +// reserved high bit, a 4-bit mode, 3 reserved bits, a 4-bit resolution, a +// 7-bit base cell and fifteen 3-bit digits. +const ( + bitSize = 64 + + modeOffset = 59 // H3_MODE_OFFSET + reservedOffset = 56 // H3_RESERVED_OFFSET + resolutionOffset = 52 // H3_RES_OFFSET + baseCellOffset = 45 // H3_BC_OFFSET + perDigitOffset = 3 // H3_PER_DIGIT_OFFSET + + // Field masks, applied after shifting the field down. + modeMask = 0xF + reservedMask = 0x7 + resolutionMask = 0xF + baseCellMask = 0x7F + digitMask = 1< 1 || uint(c.j) > 1 || uint(c.k) > 1 { - return invalidDigit + return numDigits } return unitIjkToDigitLUT[c.i][c.j][c.k] @@ -305,7 +302,7 @@ func (c coordIJK) downAp3r() coordIJK { // digit direction. The center and invalid digits leave the coordinate // unchanged. func (c coordIJK) neighbor(digit int) coordIJK { - if digit > centerDigit && digit < invalidDigit { + if digit > centerDigit && digit < numDigits { c = c.add(unitVecs[digit]).normalize() } @@ -580,7 +577,7 @@ func rotate60cw(digit int) int { var digitRotate60ccw = digitRotations() // digitRotations builds digitRotate60ccw by composing single rotations. -func digitRotations() (ccw [6][invalidDigit + 1]int) { +func digitRotations() (ccw [6][numDigits + 1]int) { for digit := range ccw[0] { ccw[0][digit] = digit } @@ -807,7 +804,7 @@ func (c Cell) toFaceIjkWithInitializedFijk(fijk faceIJK) (faceIJK, bool) { ijk = coordIJK{i: 3*ijk.i + ijk.k, j: ijk.i + 3*ijk.j, k: ijk.j + 3*ijk.k} } - if digit := indexDigit(c, r); digit > centerDigit && digit < invalidDigit { + if digit := indexDigit(c, r); digit > centerDigit && digit < numDigits { unit := unitVecs[digit] ijk = coordIJK{i: ijk.i + unit.i, j: ijk.j + unit.j, k: ijk.k + unit.k} } diff --git a/grid.go b/grid.go index d1f5cea..5c22361 100644 --- a/grid.go +++ b/grid.go @@ -123,7 +123,7 @@ func baseCellIsCwOffset(baseCell, testFace int) bool { func (c Cell) neighborRotations(dir, rotations int) (Cell, int, error) { current := c - if dir < centerDigit || dir >= invalidDigit { + if dir < centerDigit || dir >= numDigits { return 0, 0, ErrFailed } @@ -162,7 +162,7 @@ func (c Cell) neighborRotations(dir, rotations int) (Cell, int, error) { } oldDigit := indexDigit(current, res+1) - if oldDigit == invalidDigit { + if oldDigit == numDigits { return 0, 0, ErrCellInvalid } @@ -661,7 +661,7 @@ func (c Cell) IsNeighbor(other Cell) (bool, error) { return true, nil } - if originResDigit >= invalidDigit { + if originResDigit >= numDigits { return false, ErrCellInvalid } @@ -709,7 +709,7 @@ func (c Cell) IsNeighbor(other Cell) (bool, error) { } // directionForNeighbor returns the digit direction from origin to destination, -// the reverse of stepping a neighbor. It returns invalidDigit when the cells are +// the reverse of stepping a neighbor. It returns numDigits when the cells are // not neighbors. It checks each neighbor in turn, skipping the center (which is // the origin) and the deleted k axis for pentagons. func (c Cell) directionForNeighbor(destination Cell) int { @@ -725,7 +725,7 @@ func (c Cell) directionForNeighbor(destination Cell) int { } } - return invalidDigit + return numDigits } // baseCellToCCWrot60 returns the number of 60° counterclockwise rotations for a diff --git a/grid_test.go b/grid_test.go index c5cd701..c399f0d 100644 --- a/grid_test.go +++ b/grid_test.go @@ -312,8 +312,8 @@ func TestNeighborRotationsErrors(t *testing.T) { origin := CellFromString("8928308280fffff") - if _, _, err := origin.neighborRotations(invalidDigit, 0); !errors.Is(err, ErrFailed) { - t.Fatalf("neighborRotations(invalidDigit): got %v, want ErrFailed", err) + if _, _, err := origin.neighborRotations(numDigits, 0); !errors.Is(err, ErrFailed) { + t.Fatalf("neighborRotations(numDigits): got %v, want ErrFailed", err) } if _, _, err := origin.neighborRotations(-1, 0); !errors.Is(err, ErrFailed) { @@ -325,7 +325,7 @@ func TestNeighborRotationsErrors(t *testing.T) { t.Fatalf("neighborRotations(bad base cell): got %v, want ErrCellInvalid", err) } - badDigit := origin.setIndexDigit(origin.Resolution(), invalidDigit) + badDigit := origin.setIndexDigit(origin.Resolution(), numDigits) if _, _, err := badDigit.neighborRotations(kAxesDigit, 0); !errors.Is(err, ErrCellInvalid) { t.Fatalf("neighborRotations(invalid digit): got %v, want ErrCellInvalid", err) } @@ -348,8 +348,8 @@ func TestGridDefensiveErrors(t *testing.T) { // firstStep fails on the move to the next ring; secondStep passes that move // but fails while tracing the ring. Together they exercise both neighbor-step // error returns in the fast disk and ring traversals. - firstStep := base.setIndexDigit(9, invalidDigit) - secondStep := base.setIndexDigit(8, invalidDigit) + firstStep := base.setIndexDigit(9, numDigits) + secondStep := base.setIndexDigit(8, numDigits) for _, malformed := range []Cell{firstStep, secondStep} { if _, err := malformed.GridDiskDistancesUnsafe(1); err == nil { @@ -487,7 +487,7 @@ func TestIsNeighborDefensive(t *testing.T) { base := CellFromString("8928308280fffff") // Same res-8 parent, but the origin's finest digit is the unused sentinel. - invalidChild := base.setIndexDigit(9, invalidDigit) + invalidChild := base.setIndexDigit(9, numDigits) sibling := base.setIndexDigit(9, jAxesDigit) if _, err := invalidChild.IsNeighbor(sibling); !errors.Is(err, ErrCellInvalid) { @@ -511,7 +511,7 @@ func TestIsNeighborDefensive(t *testing.T) { // A malformed origin with a different parent than the target skips the // optimization and fails in the general disk fallback. - malformed := base.setIndexDigit(9, invalidDigit) + malformed := base.setIndexDigit(9, numDigits) far, err := LatLngToCell(LatLng{Lat: -33.8688, Lng: 151.2093}, 9) if err != nil { diff --git a/hierarchy.go b/hierarchy.go index e4f7589..2ec7647 100644 --- a/hierarchy.go +++ b/hierarchy.go @@ -21,13 +21,6 @@ import ( "slices" ) -// Child-set sizes: a hexagon has 7 immediate children, a pentagon 6 (its -// K-axis child is deleted). -const ( - numHexChildren = 7 - numPentChildren = 6 -) - // setResolution returns the cell with its resolution field set to res. func (c Cell) setResolution(res int) Cell { c &= ^(Cell(resolutionMask) << resolutionOffset) @@ -171,7 +164,7 @@ func (c Cell) childCells(childRes int) iter.Seq[Cell] { break } - if indexDigit(cur, i) == invalidDigit { + if indexDigit(cur, i) == numDigits { cur = cur.incrementResDigit(i) // zeroes digit i and carries into i-1 } else { break @@ -186,7 +179,7 @@ func (c Cell) childCells(childRes int) iter.Seq[Cell] { } // incrementResDigit returns h with the digit at res incremented by one. A digit -// that overflows past 6 becomes invalidDigit (7); incrementing again zeroes it +// that overflows past 6 becomes numDigits (7); incrementing again zeroes it // and carries into the next coarser digit. func (c Cell) incrementResDigit(res int) Cell { var val Cell = 1 @@ -236,7 +229,7 @@ func (c Cell) ChildPos(parentRes int) (int, error) { parentIsPentagon = parent.IsPentagon() rawDigit := indexDigit(c, res) - if rawDigit == invalidDigit || (parentIsPentagon && rawDigit == kAxesDigit) { + if rawDigit == numDigits || (parentIsPentagon && rawDigit == kAxesDigit) { return 0, ErrCellInvalid } @@ -259,7 +252,7 @@ func (c Cell) ChildPos(parentRes int) (int, error) { // Hexagon offsets are simple powers of 7. for res := childRes; res > parentRes; res-- { digit := indexDigit(c, res) - if digit == invalidDigit { + if digit == numDigits { return 0, ErrCellInvalid } out += int64(digit) * pow7[childRes-res] @@ -456,8 +449,8 @@ func CompactCells(in []Cell) ([]Cell, error) { // hexagons. func (c Cell) fullChildCount() int { if c.IsPentagon() { - return numPentChildren + return 6 } - return numHexChildren + return 7 } diff --git a/hierarchy_test.go b/hierarchy_test.go index 664b9a1..718789a 100644 --- a/hierarchy_test.go +++ b/hierarchy_test.go @@ -379,7 +379,7 @@ func TestCellToChildPosInvalidDigit(t *testing.T) { t.Parallel() // Hexagon base cell 0, res-2, with an unused (7) digit in a used slot. - cell := setH3Index(2, 0, centerDigit).setIndexDigit(2, invalidDigit) + cell := setH3Index(2, 0, centerDigit).setIndexDigit(2, numDigits) if _, err := CellToChildPos(cell, 1); !errors.Is(err, ErrCellInvalid) { t.Fatalf("CellToChildPos(hexagon invalid digit): got %v, want ErrCellInvalid", err) } diff --git a/index.go b/index.go index 347486a..de64b93 100644 --- a/index.go +++ b/index.go @@ -43,29 +43,6 @@ const ( vertexMode // H3_VERTEX_MODE ) -// Index-format constants shared by the introspection and hierarchy helpers. -const ( - base16 = 16 - bitSize = 64 - - // reservedOffset is the bit offset of the reserved field (H3_RESERVED_OFFSET). - reservedOffset = 56 - // baseCellMask masks the 7-bit base cell field after shifting. - baseCellMask = 0x7F - // modeMask masks the 4-bit mode field after shifting. - modeMask = 0xF - // reservedMask masks the 3-bit reserved field after shifting. - reservedMask = 0x7 - - // digitRegionOffset is the number of non-digit bits above the 15×3-bit digit - // region (high + mode + reserved + resolution + base cell = 19). - digitRegionOffset = bitSize - MaxResolution*perDigitOffset - - // validCellTopBits is the expected value of the top 8 bits of a valid cell: - // high bit=0, mode=1 (cell), reserved=000. - validCellTopBits = 0b00001000 -) - // pow7 holds precomputed powers of 7: pow7[i] == 7^i for i in [0, MaxResolution]. var pow7 = [MaxResolution + 1]int64{ 1, @@ -244,7 +221,7 @@ func ConstructCell(res, baseCell int, digits []int) (Cell, error) { provided := min(len(digits), res) for i, digit := range digits[:provided] { - if digit < centerDigit || digit >= invalidDigit { + if digit < centerDigit || digit >= numDigits { return 0, ErrDigitDomain } diff --git a/index_test.go b/index_test.go index bee736f..a9691db 100644 --- a/index_test.go +++ b/index_test.go @@ -260,7 +260,7 @@ func TestConstructCell(t *testing.T) { "digits_shorter_than_res_pads_centers": {giveRes: 3, giveBaseCell: 0, giveDigits: []int{0, 0}, wantCell: setH3Index(3, 0, 0)}, "digits_empty_pads_centers": {giveRes: 2, giveBaseCell: 0, giveDigits: nil, wantCell: setH3Index(2, 0, 0)}, "digit_negative": {giveRes: 1, giveBaseCell: 0, giveDigits: []int{-1}, wantErr: ErrDigitDomain}, - "digit_is_invalid": {giveRes: 1, giveBaseCell: 0, giveDigits: []int{invalidDigit}, wantErr: ErrDigitDomain}, + "digit_is_invalid": {giveRes: 1, giveBaseCell: 0, giveDigits: []int{numDigits}, wantErr: ErrDigitDomain}, "pentagon_deleted_k_axis": {giveRes: 1, giveBaseCell: 4, giveDigits: []int{kAxesDigit}, wantErr: ErrDeletedDigit}, "pentagon_deleted_below_centers": {giveRes: 3, giveBaseCell: 4, giveDigits: []int{0, 0, kAxesDigit}, wantErr: ErrDeletedDigit}, "res_zero_ignores_nil_digits": {giveRes: 0, giveBaseCell: 14, giveDigits: nil, wantCell: setH3Index(0, 14, 0)}, diff --git a/localij.go b/localij.go index 4424647..a75f9df 100644 --- a/localij.go +++ b/localij.go @@ -29,7 +29,7 @@ type CoordIJ struct { } // getBaseCellDirection returns the digit direction from one base cell to a -// neighboring base cell, or invalidDigit if they are not neighbors. +// neighboring base cell, or numDigits if they are not neighbors. func getBaseCellDirection(originBaseCell, neighborBaseCell int) int { for dir := centerDigit; dir < numDigits; dir++ { if baseCellNeighbors[originBaseCell][dir] == neighborBaseCell { @@ -37,7 +37,7 @@ func getBaseCellDirection(originBaseCell, neighborBaseCell int) int { } } - return invalidDigit + return numDigits } // cubeRound rounds floating-point cube coordinates to the nearest valid integer @@ -86,7 +86,7 @@ func (c Cell) cellToLocalIjk(target Cell) (coordIJK, error) { if originBaseCell != baseCell { dir = getBaseCellDirection(originBaseCell, baseCell) - if dir == invalidDigit { + if dir == numDigits { return coordIJK{}, ErrFailed } @@ -130,7 +130,7 @@ func (c Cell) cellToLocalIjk(target Cell) (coordIJK, error) { if originOnPent { originLeadingDigit := c.leadingNonZeroDigit() - if originLeadingDigit == invalidDigit { + if originLeadingDigit == numDigits { return coordIJK{}, ErrCellInvalid } @@ -142,7 +142,7 @@ func (c Cell) cellToLocalIjk(target Cell) (coordIJK, error) { pentagonRots = directionRots } else if indexOnPent { indexLeadingDigit := index.leadingNonZeroDigit() - if indexLeadingDigit == invalidDigit { + if indexLeadingDigit == numDigits { return coordIJK{}, ErrCellInvalid } @@ -181,7 +181,7 @@ func (c Cell) cellToLocalIjk(target Cell) (coordIJK, error) { originLeadingDigit := c.leadingNonZeroDigit() indexLeadingDigit := index.leadingNonZeroDigit() - if originLeadingDigit == invalidDigit || indexLeadingDigit == invalidDigit { + if originLeadingDigit == numDigits || indexLeadingDigit == numDigits { return coordIJK{}, ErrCellInvalid } @@ -217,7 +217,7 @@ func (c Cell) localIjkToCell(ijk coordIJK) (Cell, error) { // res 0 / base cell case. if res == 0 { dir := ijk.unitToDigit() - if dir == invalidDigit { + if dir == numDigits { return 0, ErrFailed } @@ -272,7 +272,7 @@ func (c Cell) localIjkToCell(ijk coordIJK) (Cell, error) { originLeadingDigit := c.leadingNonZeroDigit() indexLeadingDigit := out.leadingNonZeroDigit() - if originLeadingDigit == invalidDigit || indexLeadingDigit == invalidDigit { + if originLeadingDigit == numDigits || indexLeadingDigit == numDigits { return 0, ErrCellInvalid } @@ -306,7 +306,7 @@ func (c Cell) localIjkToCellOffOrigin(out Cell, dir, originBaseCell, baseCell in if originOnPent { originLeadingDigit := c.leadingNonZeroDigit() - if originLeadingDigit == invalidDigit { + if originLeadingDigit == numDigits { return 0, 0, ErrCellInvalid } diff --git a/localij_test.go b/localij_test.go index bf0175a..6a087cc 100644 --- a/localij_test.go +++ b/localij_test.go @@ -542,7 +542,7 @@ func TestLocalIJDefensive(t *testing.T) { neighbor := res2Cell(neighborBC) // A pentagon index whose leading digit is the unused sentinel. - corruptLead := pentagon.setIndexDigit(1, invalidDigit) + corruptLead := pentagon.setIndexDigit(1, numDigits) // A pentagon index whose leading digit is the deleted k axis. corruptK := pentagon.setIndexDigit(1, kAxesDigit) @@ -676,7 +676,7 @@ func TestGridPathInterpolateError(t *testing.T) { t.Fatalf("Pentagons(2): %v", err) } - corrupt := pent[0].setIndexDigit(1, invalidDigit) + corrupt := pent[0].setIndexDigit(1, numDigits) out := make([]Cell, 2) if err := gridPathInterpolate(corrupt, pent[0], 1, out, 0, 1); !errors.Is(err, ErrCellInvalid) { diff --git a/polyfill.go b/polyfill.go index a1ebd21..6c317f2 100644 --- a/polyfill.go +++ b/polyfill.go @@ -298,10 +298,10 @@ func nextCell(cell Cell) Cell { return baseCellNumToCell(cell.BaseCellNumber() + 1) } - parent := cell.setResolution(res-1).setIndexDigit(res, digitMask) + parent := cell.setResolution(res-1).setIndexDigit(res, numDigits) digit := indexDigit(cell, res) - if digit < invalidDigit-1 { + if digit < numDigits-1 { step := 1 // Skip the missing center child of a pentagon. if parent.IsPentagon() && digit == centerDigit { diff --git a/sets_test.go b/sets_test.go index 92aac23..9535759 100644 --- a/sets_test.go +++ b/sets_test.go @@ -42,8 +42,8 @@ func TestSetH3IndexValue(t *testing.T) { } for r := 6; r <= MaxResolution; r++ { - if indexDigit(h, r) != invalidDigit { - t.Fatalf("digit %d = %d, want %d", r, indexDigit(h, r), invalidDigit) + if indexDigit(h, r) != numDigits { + t.Fatalf("digit %d = %d, want %d", r, indexDigit(h, r), numDigits) } } diff --git a/vertex.go b/vertex.go index 3b67f04..cc4e58c 100644 --- a/vertex.go +++ b/vertex.go @@ -54,11 +54,11 @@ var ( // directionToVertexNumHex maps a hexagon direction digit to its first vertex // number (same face). Direction 0 (center) is unused. - directionToVertexNumHex = [numDigits]int{invalidDigit, 3, 1, 2, 5, 4, 0} + directionToVertexNumHex = [numDigits]int{numDigits, 3, 1, 2, 5, 4, 0} // directionToVertexNumPent maps a pentagon direction digit to its first vertex // number (same face). Directions 0 (center) and 1 (deleted K axis) are unused. - directionToVertexNumPent = [numDigits]int{invalidDigit, invalidDigit, 1, 2, 4, 3, 0} + directionToVertexNumPent = [numDigits]int{numDigits, numDigits, 1, 2, 4, 3, 0} // vertexNumToDirectionHex maps a hexagon vertex number to its direction digit. vertexNumToDirectionHex = [numHexVerts]int{ @@ -73,7 +73,7 @@ var ( // revNeighborDirectionsHex maps a direction digit to the reverse direction from // the neighbor in that direction, given as an index into directions to allow // rotation. Direction 0 (center) is unused. - revNeighborDirectionsHex = [numDigits]int{invalidDigit, 5, 3, 4, 1, 0, 2} + revNeighborDirectionsHex = [numDigits]int{numDigits, 5, 3, 4, 1, 0, 2} ) // vertexRotations returns the number of counterclockwise 60° rotations of the @@ -129,7 +129,7 @@ func (c Cell) vertexRotations() (int, error) { // sequence. It returns invalidVertexNum if the direction is not valid. func (c Cell) vertexNumForDirection(direction int) int { isPent := c.IsPentagon() - if direction == centerDigit || direction >= invalidDigit || (isPent && direction == kAxesDigit) { + if direction == centerDigit || direction >= numDigits || (isPent && direction == kAxesDigit) { return invalidVertexNum } @@ -146,7 +146,7 @@ func (c Cell) vertexNumForDirection(direction int) int { } // directionForVertexNum returns the direction for the neighbor between the given -// vertex number and the next in sequence, or invalidDigit if the vertex number +// vertex number and the next in sequence, or numDigits if the vertex number // is not valid for the cell. func (c Cell) directionForVertexNum(vertexNum int) int { isPent := c.IsPentagon() @@ -157,12 +157,12 @@ func (c Cell) directionForVertexNum(vertexNum int) int { } if vertexNum < 0 || vertexNum > maxVerts-1 { - return invalidDigit + return numDigits } rotations, err := c.vertexRotations() if err != nil { - return invalidDigit + return numDigits } if isPent { @@ -216,7 +216,7 @@ func (c Cell) vertexOwner(vertexNum, cellNumVerts, res int) (Cell, int, error) { // Left neighbor of the vertex, with its rotations. left := c.directionForVertexNum(vertexNum) - if left == invalidDigit { + if left == numDigits { return 0, 0, ErrFailed } diff --git a/vertex_test.go b/vertex_test.go index 4b810c3..03f02b3 100644 --- a/vertex_test.go +++ b/vertex_test.go @@ -116,7 +116,7 @@ func TestVertexNumForDirectionInvalid(t *testing.T) { t.Fatalf("vertexNumForDirection(center): got %d, want %d", got, invalidVertexNum) } - if got := hexagon.vertexNumForDirection(invalidDigit); got != invalidVertexNum { + if got := hexagon.vertexNumForDirection(numDigits); got != invalidVertexNum { t.Fatalf("vertexNumForDirection(invalid): got %d, want %d", got, invalidVertexNum) } @@ -135,12 +135,12 @@ func TestDirectionForVertexNumInvalid(t *testing.T) { t.Parallel() hexagon := CellFromString("8928308280fffff") - if got := hexagon.directionForVertexNum(-1); got != invalidDigit { - t.Fatalf("directionForVertexNum(-1): got %d, want %d", got, invalidDigit) + if got := hexagon.directionForVertexNum(-1); got != numDigits { + t.Fatalf("directionForVertexNum(-1): got %d, want %d", got, numDigits) } - if got := hexagon.directionForVertexNum(numHexVerts); got != invalidDigit { - t.Fatalf("directionForVertexNum(6): got %d, want %d", got, invalidDigit) + if got := hexagon.directionForVertexNum(numHexVerts); got != numDigits { + t.Fatalf("directionForVertexNum(6): got %d, want %d", got, numDigits) } } @@ -159,8 +159,8 @@ func TestVertexRotationsError(t *testing.T) { t.Fatalf("vertexNumForDirection(corrupt): got %d, want %d", got, invalidVertexNum) } - if got := corrupt.directionForVertexNum(0); got != invalidDigit { - t.Fatalf("directionForVertexNum(corrupt): got %d, want %d", got, invalidDigit) + if got := corrupt.directionForVertexNum(0); got != numDigits { + t.Fatalf("directionForVertexNum(corrupt): got %d, want %d", got, numDigits) } }