github.com/liucxer/courier@v1.7.1/h3/h3_index_test.go (about)

     1  package h3
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  )
     9  
    10  const deg2rad = math.Pi / 180.0
    11  const rad2deg = 180.0 / math.Pi
    12  
    13  func GeoFromWGS84(lat float64, lon float64) *GeoCoord {
    14  	return &GeoCoord{lat * deg2rad, lon * deg2rad}
    15  }
    16  
    17  func Test_geoToH3(t *testing.T) {
    18  
    19  	t.Run("geoToH3", func(t *testing.T) {
    20  		require.Equal(t, H3Index(614553222213795839), geoToH3(GeoFromWGS84(0, 0), 8))
    21  		require.Equal(t, H3Index(613287273236004863), geoToH3(GeoFromWGS84(45, 45), 8))
    22  		require.Equal(t, H3Index(612544946678792191), geoToH3(GeoFromWGS84(90, 90), 8))
    23  	})
    24  
    25  	t.Run("geoToH3 2", func(t *testing.T) {
    26  		g := GeoCoord{0.659966917655, 2*3.14159 - 2.1364398519396}
    27  
    28  		expects := []uint64{
    29  			577199624117288959,
    30  			581672437419081727,
    31  			586175487290638335,
    32  			590678605881671679,
    33  			595182179739238399,
    34  			599685771850416127,
    35  			604189370672480255,
    36  			608692970266296319,
    37  			613196569891569663,
    38  			617700169517629439,
    39  			622203769144967167,
    40  			626707368772308991,
    41  			631210968399678463,
    42  			635714568027048703,
    43  			640218167654419175,
    44  			644721767281789666, // max 15
    45  			0,
    46  			0,
    47  			0,
    48  		}
    49  
    50  		for i := range expects {
    51  			require.Equal(t, H3Index(expects[i]), geoToH3(&g, i))
    52  		}
    53  	})
    54  
    55  	t.Run("_h3ToFaceIjk & _faceIjkToH3", func(t *testing.T) {
    56  		h := H3Index(613219835716829183)
    57  		f := FaceIJK{}
    58  		_h3ToFaceIjk(h, &f)
    59  		require.Equal(t, h, _faceIjkToH3(&f, 8))
    60  	})
    61  
    62  	t.Run("_geoToFaceIjk & _faceIjkToGeo", func(t *testing.T) {
    63  		g := GeoCoord{0, 0}
    64  
    65  		f := FaceIJK{}
    66  		_geoToFaceIjk(&g, 15, &f)
    67  		g2 := GeoCoord{}
    68  		_faceIjkToGeo(&f, 15, &g2)
    69  	})
    70  }
    71  
    72  func Test_getPentagonIndexes(t *testing.T) {
    73  	expectedCount := pentagonIndexCount()
    74  
    75  	for res := 0; res <= 15; res++ {
    76  		h3Indexes := make([]H3Index, 0)
    77  		getPentagonIndexes(res, &h3Indexes)
    78  
    79  		for _, h3Index := range h3Indexes {
    80  			if h3Index != 0 {
    81  				require.True(t, h3IsValid(h3Index), "index should be valid")
    82  				require.True(t, h3IsPentagon(h3Index), "index should be pentagon")
    83  				require.True(t, h3GetResolution(h3Index) == res, "index should have correct resolution")
    84  			}
    85  		}
    86  
    87  		require.True(t, len(h3Indexes) == expectedCount, "there should be exactly 12 pentagons")
    88  	}
    89  }
    90  
    91  func Test_GeoToH3(t *testing.T) {
    92  	t.Run("geoToH3ExtremeCoordinates", func(t *testing.T) {
    93  		// Check that none of these cause crashes.
    94  		g := GeoCoord{0, 1e45}
    95  		t.Log(geoToH3(&g, 14)) // 641677981140798679
    96  
    97  		g2 := GeoCoord{1e46, 1e45}
    98  		t.Log(geoToH3(&g2, 15))
    99  
   100  		var g4 GeoCoord
   101  		setGeoDegs(&g4, 2, -3e39)
   102  
   103  		t.Log(geoToH3(&g4, 0))
   104  	})
   105  
   106  	t.Run("faceIjkToH3ExtremeCoordinates", func(t *testing.T) {
   107  		fijk0I := FaceIJK{0, CoordIJK{3, 0, 0}}
   108  		require.True(t, _faceIjkToH3(&fijk0I, 0) == 0, "i out of bounds at res 0")
   109  
   110  		fijk0J := FaceIJK{1, CoordIJK{0, 4, 0}}
   111  		require.True(t, _faceIjkToH3(&fijk0J, 0) == 0, "j out of bounds at res 0")
   112  
   113  		fijk0K := FaceIJK{2, CoordIJK{2, 0, 5}}
   114  		require.True(t, _faceIjkToH3(&fijk0K, 0) == 0, "k out of bounds at res 0")
   115  
   116  		fijk1I := FaceIJK{3, CoordIJK{6, 0, 0}}
   117  		require.True(t, _faceIjkToH3(&fijk1I, 1) == 0, "i out of bounds at res 1")
   118  
   119  		fijk1J := FaceIJK{4, CoordIJK{0, 7, 1}}
   120  		require.True(t, _faceIjkToH3(&fijk1J, 1) == 0, "j out of bounds at res 1")
   121  
   122  		fijk1K := FaceIJK{5, CoordIJK{2, 0, 8}}
   123  		require.True(t, _faceIjkToH3(&fijk1K, 1) == 0, "k out of bounds at res 1")
   124  
   125  		fijk2I := FaceIJK{6, CoordIJK{18, 0, 0}}
   126  		require.True(t, _faceIjkToH3(&fijk2I, 2) == 0, "i out of bounds at res 2")
   127  
   128  		fijk2J := FaceIJK{7, CoordIJK{0, 19, 1}}
   129  		require.True(t, _faceIjkToH3(&fijk2J, 2) == 0, "j out of bounds at res 2")
   130  
   131  		fijk2K := FaceIJK{8, CoordIJK{2, 0, 20}}
   132  		require.True(t, _faceIjkToH3(&fijk2K, 2) == 0, "k out of bounds at res 2")
   133  	})
   134  
   135  	t.Run("h3IsValidAtResolution", func(t *testing.T) {
   136  		for i := 0; i <= MAX_H3_RES; i++ {
   137  			geoCoord := GeoCoord{0, 0}
   138  
   139  			h3 := geoToH3(&geoCoord, i)
   140  
   141  			require.True(t, h3IsValid(h3), "h3IsValid failed on resolution %d", i)
   142  		}
   143  	})
   144  
   145  	t.Run("h3IsValidDigits", func(t *testing.T) {
   146  		geoCoord := GeoCoord{0, 0}
   147  		h3 := geoToH3(&geoCoord, 1)
   148  		// Set a bit for an unused digit to something else.
   149  		h3 ^= 1
   150  		require.True(t, !h3IsValid(h3), "h3IsValid failed on invalid unused digits")
   151  	})
   152  
   153  	t.Run("h3IsValidBaseCell", func(t *testing.T) {
   154  		for i := 0; i < NUM_BASE_CELLS; i++ {
   155  			h := H3_INIT
   156  			H3_SET_MODE(&h, H3_HEXAGON_MODE)
   157  			H3_SET_BASE_CELL(&h, i)
   158  			require.True(t, h3IsValid(h), "h3IsValid failed on base cell %d", i)
   159  			require.True(t, h3GetBaseCell(h) == i,
   160  				"failed to recover base cell")
   161  		}
   162  	})
   163  
   164  	t.Run("h3IsValidBaseCellInvalid", func(t *testing.T) {
   165  		hWrongBaseCell := H3_INIT
   166  		H3_SET_MODE(&hWrongBaseCell, H3_HEXAGON_MODE)
   167  		H3_SET_BASE_CELL(&hWrongBaseCell, NUM_BASE_CELLS)
   168  		require.True(t, !h3IsValid(hWrongBaseCell), "h3IsValid failed on invalid base cell")
   169  	})
   170  
   171  	t.Run("h3IsValidWithMode", func(t *testing.T) {
   172  		for i := 0; i <= 0xf; i++ {
   173  			h := H3_INIT
   174  			H3_SET_MODE(&h, H3Mode(i))
   175  			require.True(t, !h3IsValid(h) || i == 1, "h3IsValid failed on mode %d", i)
   176  		}
   177  	})
   178  
   179  	t.Run("h3BadDigitInvalid", func(t *testing.T) {
   180  		h := H3_INIT
   181  		// By default the first index digit is out of range.
   182  		H3_SET_MODE(&h, H3_HEXAGON_MODE)
   183  		H3_SET_RESOLUTION(&h, 1)
   184  		require.True(t, !h3IsValid(h), "h3IsValid failed on too large digit")
   185  	})
   186  
   187  	t.Run("h3DeletedSubsequenceInvalid", func(t *testing.T) {
   188  		var h H3Index
   189  		// Create an index located in a deleted subsequence of a pentagon.
   190  		setH3Index(&h, 1, 4, K_AXES_DIGIT)
   191  
   192  		require.True(t, !h3IsValid(h), "h3IsValid failed on deleted subsequence")
   193  	})
   194  
   195  	t.Run("h3ToString", func(t *testing.T) {
   196  		require.Equal(t, "880a000001fffff", h3ToString(612665471184928767))
   197  	})
   198  
   199  	t.Run("stringToH3", func(t *testing.T) {
   200  		require.Equal(t, H3Index(612665471184928767), stringToH3("880a000001fffff"))
   201  		require.True(t, stringToH3("") == 0, "got an index from nothing")
   202  		require.True(t, stringToH3("**") == 0, "got an index from junk")
   203  		require.True(t, stringToH3("ffffffffffffffff") == 0xffffffffffffffff, "failed on large input")
   204  	})
   205  
   206  	t.Run("setH3Index", func(t *testing.T) {
   207  		var h H3Index
   208  		setH3Index(&h, 5, 12, 1)
   209  
   210  		require.True(t, H3_GET_RESOLUTION(h) == 5, "resolution as expected")
   211  		require.True(t, H3_GET_BASE_CELL(h) == 12, "base cell as expected")
   212  		require.True(t, H3_GET_MODE(h) == H3_HEXAGON_MODE, "mode as expected")
   213  
   214  		for i := 1; i <= 5; i++ {
   215  			require.True(t, H3_GET_INDEX_DIGIT(h, i) == 1, "digit as expected")
   216  		}
   217  
   218  		for i := 6; i <= MAX_H3_RES; i++ {
   219  			require.True(t, H3_GET_INDEX_DIGIT(h, i) == 7, "blanked digit as expected")
   220  		}
   221  
   222  		require.True(t, h == 599405990164561919, "index matches expected")
   223  		require.True(t, h == 0x85184927fffffff, "index matches expected")
   224  	})
   225  
   226  	t.Run("h3IsResClassIII", func(t *testing.T) {
   227  		coord := GeoCoord{0, 0}
   228  		for i := 0; i <= MAX_H3_RES; i++ {
   229  			h := geoToH3(&coord, i)
   230  			require.True(t, h3IsResClassIII(h) == isResClassIII(i),
   231  				"matches existing definition")
   232  		}
   233  	})
   234  }
   235  
   236  func Test_h3ToCenterChild(t *testing.T) {
   237  	var baseHex H3Index
   238  	var baseCentroid GeoCoord
   239  
   240  	setH3Index(&baseHex, 8, 4, 2)
   241  	h3ToGeo(baseHex, &baseCentroid)
   242  
   243  	t.Run("propertyTests", func(t *testing.T) {
   244  		for res := 0; res <= MAX_H3_RES-1; res++ {
   245  			for childRes := res + 1; childRes <= MAX_H3_RES; childRes++ {
   246  				var centroid GeoCoord
   247  				h3Index := geoToH3(&baseCentroid, res)
   248  				h3ToGeo(h3Index, &centroid)
   249  				geoChild := geoToH3(&centroid, childRes)
   250  				centerChild := h3ToCenterChild(h3Index, childRes)
   251  				require.True(t, centerChild == geoChild, "center child should be same as indexed centroid at child resolution")
   252  				require.True(t, h3GetResolution(centerChild) == childRes, "center child should have correct resolution")
   253  				require.True(t, h3ToParent(centerChild, res) == h3Index, "parent at original resolution should be initial index")
   254  			}
   255  		}
   256  	})
   257  
   258  	t.Run("sameRes", func(t *testing.T) {
   259  		res := h3GetResolution(baseHex)
   260  		require.True(t, h3ToCenterChild(baseHex, res) == baseHex, "center child at same resolution should return self")
   261  	})
   262  
   263  	t.Run("invalidInputs", func(t *testing.T) {
   264  		res := h3GetResolution(baseHex)
   265  		require.True(t, h3ToCenterChild(baseHex, res-1) == 0,
   266  			"should fail at coarser resolution")
   267  		require.True(t, h3ToCenterChild(baseHex, -1) == 0,
   268  			"should fail for negative resolution")
   269  		require.True(t, h3ToCenterChild(baseHex, MAX_H3_RES+1) == 0, "should fail beyond finest resolution")
   270  	})
   271  }
   272  
   273  func Test_h3ToGeoBoundary(t *testing.T) {
   274  	t.Run("h3ToGeo", func(t *testing.T) {
   275  		expectGeo := GeoFromWGS84(37.812291538780364, -122.41353593838753)
   276  
   277  		g := GeoCoord{}
   278  		h3ToGeo(613196569891569663, &g)
   279  
   280  		require.True(t, geoAlmostEqual(expectGeo, &g))
   281  	})
   282  
   283  	t.Run("h3ToGeoBoundary", func(t *testing.T) {
   284  		expect := GeoBoundary{
   285  			numVerts: 6,
   286  			Verts: []GeoCoord{
   287  				*GeoFromWGS84(37.80760100422449, -122.41208776737979),
   288  				*GeoFromWGS84(37.81114379658359, -122.40761222203226),
   289  				*GeoFromWGS84(37.815834307032965, -122.4090602822424),
   290  				*GeoFromWGS84(37.816981839321244, -122.41498422661992),
   291  				*GeoFromWGS84(37.81343893006517, -122.41945955867847),
   292  				*GeoFromWGS84(37.808748605427716, -122.41801115969699),
   293  			},
   294  		}
   295  
   296  		gb := GeoBoundary{}
   297  		h3ToGeoBoundary(H3Index(613196569891569663), &gb)
   298  
   299  		require.Equal(t, expect.numVerts, gb.numVerts)
   300  
   301  		for i := 0; i < expect.numVerts; i++ {
   302  			require.True(t, geoAlmostEqual(&expect.Verts[i], &gb.Verts[i]))
   303  		}
   304  	})
   305  
   306  	t.Run("h3ToGeoBoundary pentagon", func(t *testing.T) {
   307  		expect := GeoBoundary{
   308  			numVerts: 10,
   309  			Verts: []GeoCoord{
   310  				*GeoFromWGS84(50.104450101, -143.478843877),
   311  				*GeoFromWGS84(50.103795870, -143.480089732),
   312  				*GeoFromWGS84(50.103371455, -143.480450779),
   313  				*GeoFromWGS84(50.102409316, -143.479865681),
   314  				*GeoFromWGS84(50.102057919, -143.479347956),
   315  				*GeoFromWGS84(50.102117500, -143.477740557),
   316  				*GeoFromWGS84(50.102324725, -143.477059533),
   317  				*GeoFromWGS84(50.103323690, -143.476651121),
   318  				*GeoFromWGS84(50.103803169, -143.476747929),
   319  				*GeoFromWGS84(50.104360999, -143.478102984),
   320  			},
   321  		}
   322  
   323  		gb := GeoBoundary{}
   324  		h3ToGeoBoundary(H3Index(0x891c0000003ffff), &gb)
   325  
   326  		require.Equal(t, expect.numVerts, gb.numVerts)
   327  
   328  		for i := 0; i < expect.numVerts; i++ {
   329  			require.True(t, geoAlmostEqual(&expect.Verts[i], &gb.Verts[i]))
   330  		}
   331  	})
   332  }
   333  
   334  func Test_h3ToChildren(t *testing.T) {
   335  	sf := GeoCoord{0.659966917655, 2*3.14159 - 2.1364398519396}
   336  	sfHex8 := geoToH3(&sf, 8) // 613196569891569663
   337  
   338  	var verifyCountAndUniqueness = func(t *testing.T, children []H3Index, paddedCount int, expectedCount int) {
   339  		numFound := 0
   340  		for i := 0; i < paddedCount; i++ {
   341  			if len(children) == i {
   342  				break
   343  			}
   344  
   345  			currIndex := children[i]
   346  			if currIndex == 0 {
   347  				continue
   348  			}
   349  			numFound++
   350  
   351  			// verify uniqueness
   352  			indexSeen := 0
   353  			for j := i + 1; j < paddedCount; j++ {
   354  				if len(children) == j {
   355  					break
   356  				}
   357  
   358  				if children[j] == currIndex {
   359  					indexSeen++
   360  				}
   361  			}
   362  			require.True(t, indexSeen == 0, "index was seen only once")
   363  		}
   364  
   365  		require.True(t, numFound == expectedCount, "got expected number of children")
   366  	}
   367  
   368  	t.Run("oneResStep", func(t *testing.T) {
   369  		sfHex9s := make([]H3Index, 0)
   370  		h3ToChildren(sfHex8, 9, &sfHex9s)
   371  
   372  		var center GeoCoord
   373  		h3ToGeo(sfHex8, &center)
   374  		sfHex9_0 := geoToH3(&center, 9)
   375  
   376  		numFound := 0
   377  		for i := range sfHex9s {
   378  			if sfHex9_0 == sfHex9s[i] {
   379  				numFound++
   380  			}
   381  		}
   382  
   383  		require.True(t, numFound == 1, "found the center hex")
   384  
   385  		// Get the neighbor hexagons by averaging the center point and outer
   386  		// points then query for those independently
   387  
   388  		var outside GeoBoundary
   389  		h3ToGeoBoundary(sfHex8, &outside)
   390  
   391  		for i := 0; i < outside.numVerts; i++ {
   392  			avg := GeoCoord{}
   393  			avg.Lat = (outside.Verts[i].Lat + center.Lat) / 2
   394  			avg.Lon = (outside.Verts[i].Lon + center.Lon) / 2
   395  			avgHex9 := geoToH3(&avg, 9)
   396  			for j := range sfHex9s {
   397  				if avgHex9 == sfHex9s[j] {
   398  					numFound++
   399  				}
   400  			}
   401  		}
   402  
   403  		require.True(t, numFound == 7, "found all expected children")
   404  	})
   405  
   406  	t.Run("multipleResSteps", func(t *testing.T) {
   407  		// Lots of children. Will just confirm number and uniqueness
   408  		children := make([]H3Index, 0)
   409  		h3ToChildren(sfHex8, 10, &children)
   410  
   411  		verifyCountAndUniqueness(t, children, 60, 49)
   412  	})
   413  
   414  	t.Run("sameRes", func(t *testing.T) {
   415  		children := make([]H3Index, 0)
   416  		h3ToChildren(sfHex8, 8, &children)
   417  		verifyCountAndUniqueness(t, children, 7, 1)
   418  	})
   419  
   420  	t.Run("childResTooCoarse", func(t *testing.T) {
   421  		children := make([]H3Index, 0)
   422  		h3ToChildren(sfHex8, 7, &children)
   423  		verifyCountAndUniqueness(t, children, 7, 0)
   424  	})
   425  
   426  	t.Run("childResTooFine", func(t *testing.T) {
   427  		sfHexMax := geoToH3(&sf, MAX_H3_RES)
   428  		children := make([]H3Index, 0)
   429  		h3ToChildren(sfHexMax, MAX_H3_RES+1, &children)
   430  		verifyCountAndUniqueness(t, children, 7, 0)
   431  	})
   432  
   433  	t.Run("pentagonChildren", func(t *testing.T) {
   434  		var pentagon H3Index
   435  		setH3Index(&pentagon, 1, 4, 0)
   436  
   437  		expectedCount := (5 * 7) + 6
   438  		paddedCount := maxH3ToChildrenSize(pentagon, 3)
   439  
   440  		children := make([]H3Index, 0)
   441  
   442  		// todo don't why need this
   443  		// h3ToChildren(sfHex8, 10, &children)
   444  
   445  		h3ToChildren(pentagon, 3, &children)
   446  
   447  		verifyCountAndUniqueness(t, children, paddedCount, expectedCount)
   448  	})
   449  }
   450  
   451  func Test_maxH3ToChildrenSize(t *testing.T) {
   452  	sf := GeoCoord{0.659966917655, 2*3.14159 - 2.1364398519396}
   453  
   454  	parent := geoToH3(&sf, 7)
   455  
   456  	require.True(t, maxH3ToChildrenSize(parent, 3) == 0, "got expected size for coarser res")
   457  	require.True(t, maxH3ToChildrenSize(parent, 7) == 1, "got expected size for same res")
   458  	require.True(t, maxH3ToChildrenSize(parent, 8) == 7, "got expected size for child res")
   459  	require.True(t, maxH3ToChildrenSize(parent, 9) == 7*7, "got expected size for grandchild res")
   460  }