Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion boundary.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions boundary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
184 changes: 116 additions & 68 deletions constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<<perDigitOffset - 1

// digitRegionOffset is the number of non-digit bits above the fifteen
// 3-bit digits: high bit, mode, reserved, resolution and base cell, 19.
digitRegionOffset = bitSize - MaxResolution*perDigitOffset

// h3Init has every digit set to numDigits and every other field zero.
h3Init = 1<<(MaxResolution*perDigitOffset) - 1

// validCellTopBits is the expected value of the top 8 bits of a valid cell:
// high bit 0, mode 1 (cell), reserved 000.
validCellTopBits = 0b00001000

// base16 is the radix of an index's string form.
base16 = 16
)

// Sentinels in the lookup tables.
const (
// invalidVertexNum marks a vertex number that is not valid for a cell.
invalidVertexNum = -1
// invalidRotations marks a base cell that is not found on a queried face.
invalidRotations = -1

// invalidFace marks an unused slot while collecting a cell's icosahedron
// faces.
invalidFace = -1
// invalidBaseCell is the sentinel in baseCellNeighbors marking the missing
// neighbor across a pentagon's deleted k-axis edge.
invalidBaseCell = 127
Expand All @@ -137,15 +185,15 @@ var (

// unitIjkToDigitLUT precomputes the digit for each normalized unit IJK.
// Valid unit IJK coordinates have at most one of i/j/k as 1 and the rest 0.
// Indexed by [i][j][k] for i,j,k in {0,1}. Invalid entries map to invalidDigit.
// Indexed by [i][j][k] for i,j,k in {0,1}. Invalid entries map to numDigits.
unitIjkToDigitLUT = [2][2][2]int{
{
{centerDigit, kAxesDigit},
{jAxesDigit, jkAxesDigit},
},
{
{iAxesDigit, ikAxesDigit},
{ijAxesDigit, invalidDigit},
{ijAxesDigit, numDigits},
},
}

Expand Down
2 changes: 1 addition & 1 deletion directededge.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type DirectedEdge int64
// neighbors at the same resolution, otherwise it returns ErrNotNeighbors.
func (c Cell) DirectedEdge(other Cell) (DirectedEdge, error) {
direction := c.directionForNeighbor(other)
if direction == invalidDigit {
if direction == numDigits {
return 0, ErrNotNeighbors
}

Expand Down
4 changes: 2 additions & 2 deletions exact_projection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ func crossingPoint(oracle *exactOracle, from, to coordIJK, face, adjRes, dir int
func hexagonBoundaryPoints(oracle *exactOracle, fijk faceIJK, res int) []projectionPoint {
adjRes, verts := fijk.toVerts(res)
centerFace := fijk.face
points := make([]projectionPoint, 0, maxCellBoundaryVerts)
points := make([]projectionPoint, 0, MaxCellBndryVerts)

lastFace := -1
lastOverage := noOverage
Expand Down Expand Up @@ -439,7 +439,7 @@ func hexagonBoundaryPoints(oracle *exactOracle, fijk faceIJK, res int) []project
// pentagonBoundaryPoints mirrors faceIJK.pentToCellBoundary for a whole loop.
func pentagonBoundaryPoints(oracle *exactOracle, fijk faceIJK, res int) []projectionPoint {
adjRes, verts := fijk.pentToVerts(res)
points := make([]projectionPoint, 0, maxCellBoundaryVerts)
points := make([]projectionPoint, 0, MaxCellBndryVerts)

var lastFijk faceIJK

Expand Down
13 changes: 5 additions & 8 deletions faceijk.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ import (
"math"
)

// invalidFace marks an unused slot while collecting a cell's icosahedron faces.
const invalidFace = -1

type (
vec3d struct{ x, y, z float64 }
vec2d struct{ x, y float64 }
Expand Down Expand Up @@ -262,13 +259,13 @@ func (c coordIJK) distance(b coordIJK) int {
}

// unitToDigit maps an IJK coordinate to its digit if, once normalized, it is the
// center or one of the six unit neighbors, returning invalidDigit otherwise.
// center or one of the six unit neighbors, returning numDigits otherwise.
func (c coordIJK) unitToDigit() int {
c = c.normalize()

// Each unsigned comparison rejects both negatives and values above one.
if uint(c.i) > 1 || uint(c.j) > 1 || uint(c.k) > 1 {
return invalidDigit
return numDigits
}

return unitIjkToDigitLUT[c.i][c.j][c.k]
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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}
}
Expand Down
10 changes: 5 additions & 5 deletions grid.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -661,7 +661,7 @@ func (c Cell) IsNeighbor(other Cell) (bool, error) {
return true, nil
}

if originResDigit >= invalidDigit {
if originResDigit >= numDigits {
return false, ErrCellInvalid
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
Loading
Loading