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, ¢roid) 249 geoChild := geoToH3(¢roid, 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, ¢er) 374 sfHex9_0 := geoToH3(¢er, 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 }