github.com/liucxer/courier@v1.7.1/h3/localij.go (about) 1 package h3 2 3 import ( 4 "fmt" 5 "math" 6 ) 7 8 /** 9 * Origin leading digit . index leading digit . rotations 60 cw 10 * Either being 1 (K axis) is invalid. 11 * No good default at 0. 12 */ 13 var PENTAGON_ROTATIONS = [7][7]int{ 14 {0, -1, 0, 0, 0, 0, 0}, // 0 15 {-1, -1, -1, -1, -1, -1, -1}, // 1 16 {0, -1, 0, 0, 0, 1, 0}, // 2 17 {0, -1, 0, 0, 1, 1, 0}, // 3 18 {0, -1, 0, 5, 0, 0, 0}, // 4 19 {0, -1, 5, 5, 0, 0, 0}, // 5 20 {0, -1, 0, 0, 0, 0, 0}, // 6 21 } 22 23 /** 24 * Reverse base cell direction . leading index digit . rotations 60 ccw. 25 * For reversing the rotation introduced in PENTAGON_ROTATIONS when 26 * the origin is on a pentagon (regardless of the base cell of the index.) 27 */ 28 var PENTAGON_ROTATIONS_REVERSE = [7][7]int{ 29 {0, 0, 0, 0, 0, 0, 0}, // 0 30 {-1, -1, -1, -1, -1, -1, -1}, // 1 31 {0, 1, 0, 0, 0, 0, 0}, // 2 32 {0, 1, 0, 0, 0, 1, 0}, // 3 33 {0, 5, 0, 0, 0, 0, 0}, // 4 34 {0, 5, 0, 5, 0, 0, 0}, // 5 35 {0, 0, 0, 0, 0, 0, 0}, // 6 36 } 37 38 /** 39 * Reverse base cell direction . leading index digit . rotations 60 ccw. 40 * For reversing the rotation introduced in PENTAGON_ROTATIONS when the index is 41 * on a pentagon and the origin is not. 42 */ 43 var PENTAGON_ROTATIONS_REVERSE_NONPOLAR = [7][7]int{ 44 {0, 0, 0, 0, 0, 0, 0}, // 0 45 {-1, -1, -1, -1, -1, -1, -1}, // 1 46 {0, 1, 0, 0, 0, 0, 0}, // 2 47 {0, 1, 0, 0, 0, 1, 0}, // 3 48 {0, 5, 0, 0, 0, 0, 0}, // 4 49 {0, 1, 0, 5, 1, 1, 0}, // 5 50 {0, 0, 0, 0, 0, 0, 0}, // 6 51 } 52 53 /** 54 * Reverse base cell direction . leading index digit . rotations 60 ccw. 55 * For reversing the rotation introduced in PENTAGON_ROTATIONS when the index is 56 * on a polar pentagon and the origin is not. 57 */ 58 var PENTAGON_ROTATIONS_REVERSE_POLAR = [7][7]int{ 59 {0, 0, 0, 0, 0, 0, 0}, // 0 60 {-1, -1, -1, -1, -1, -1, -1}, // 1 61 {0, 1, 1, 1, 1, 1, 1}, // 2 62 {0, 1, 0, 0, 0, 1, 0}, // 3 63 {0, 1, 0, 0, 1, 1, 1}, // 4 64 {0, 1, 0, 5, 1, 1, 0}, // 5 65 {0, 1, 1, 0, 1, 1, 1}, // 6 66 } 67 68 /** 69 * Prohibited directions when unfolding a pentagon. 70 * 71 * Indexes by two directions, both relative to the pentagon base cell. The first 72 * is the direction of the origin index and the second is the direction of the 73 * index to unfold. Direction refers to the direction from base cell to base 74 * cell if the indexes are on different base cells, or the leading digit if 75 * within the pentagon base cell. 76 * 77 * This previously included a Class II/Class III check but these were removed 78 * due to failure cases. It's possible this could be restricted to a narrower 79 * set of a failure cases. Currently, the logic is any unfolding across more 80 * than one icosahedron face is not permitted. 81 */ 82 var FAILED_DIRECTIONS = [7][7]bool{ 83 {false, false, false, false, false, false, false}, // 0 84 {false, false, false, false, false, false, false}, // 1 85 {false, false, false, false, true, true, false}, // 2 86 {false, false, false, false, true, false, true}, // 3 87 {false, false, true, true, false, false, false}, // 4 88 {false, false, true, false, false, false, true}, // 5 89 {false, false, false, true, false, true, false}, // 6 90 } 91 92 /** 93 * Produces ijk+ coordinates for an index anchored by an origin. 94 * 95 * The coordinate space used by this function may have deleted 96 * regions or warping due to pentagonal distortion. 97 * 98 * Coordinates are only comparable if they come from the same 99 * origin index. 100 * 101 * Failure may occur if the index is too far away from the origin 102 * or if the index is on the other side of a pentagon. 103 * 104 * @param origin An anchoring index for the ijk+ coordinate system. 105 * @param index Index to find the coordinates of 106 * @param out ijk+ coordinates of the index will be placed here on success 107 * @return 0 on success, or another value on failure. 108 */ 109 func h3ToLocalIjk(origin H3Index, h3 H3Index, out *CoordIJK) int { 110 res := H3_GET_RESOLUTION(origin) 111 if res != H3_GET_RESOLUTION(h3) { 112 return 1 113 } 114 115 originBaseCell := H3_GET_BASE_CELL(origin) 116 baseCell := H3_GET_BASE_CELL(h3) 117 118 // Direction from origin base cell to index base cell 119 dir := CENTER_DIGIT 120 revDir := CENTER_DIGIT 121 if originBaseCell != baseCell { 122 dir = _getBaseCellDirection(originBaseCell, baseCell) 123 if dir == INVALID_DIGIT { 124 // Base cells are not neighbors, can't unfold. 125 return 2 126 } 127 revDir = _getBaseCellDirection(baseCell, originBaseCell) 128 } 129 130 originOnPent := _isBaseCellPentagon(originBaseCell) 131 indexOnPent := _isBaseCellPentagon(baseCell) 132 indexFijk := FaceIJK{} 133 if dir != CENTER_DIGIT { 134 // Rotate index into the orientation of the origin base cell. 135 // cw because we are undoing the rotation into that base cell. 136 baseCellRotations := baseCellNeighbor60CCWRots[originBaseCell][dir] 137 if indexOnPent { 138 for i := 0; i < baseCellRotations; i++ { 139 h3 = _h3RotatePent60cw(h3) 140 revDir = _rotate60cw(revDir) 141 if revDir == K_AXES_DIGIT { 142 revDir = _rotate60cw(revDir) 143 } 144 } 145 } else { 146 for i := 0; i < baseCellRotations; i++ { 147 h3 = _h3Rotate60cw(h3) 148 revDir = _rotate60cw(revDir) 149 } 150 } 151 } 152 153 // Face is unused. This produces coordinates in base cell coordinate space. 154 _h3ToFaceIjkWithInitializedFijk(h3, &indexFijk) 155 if dir != CENTER_DIGIT { 156 if baseCell == originBaseCell { 157 panic(fmt.Errorf("baseCell should not equal originBaseCell")) 158 } 159 160 pentagonRotations := 0 161 directionRotations := 0 162 if originOnPent { 163 originLeadingDigit := _h3LeadingNonZeroDigit(origin) 164 if FAILED_DIRECTIONS[originLeadingDigit][dir] { 165 // TODO: We may be unfolding the pentagon incorrectly in this 166 // case; return an error code until this is guaranteed to be 167 // correct. 168 return 3 169 } 170 171 directionRotations = PENTAGON_ROTATIONS[originLeadingDigit][dir] 172 pentagonRotations = directionRotations 173 } else if indexOnPent { 174 indexLeadingDigit := _h3LeadingNonZeroDigit(h3) 175 if FAILED_DIRECTIONS[indexLeadingDigit][revDir] { 176 // TODO: We may be unfolding the pentagon incorrectly in this 177 // case; return an error code until this is guaranteed to be 178 // correct. 179 return 4 180 } 181 182 pentagonRotations = PENTAGON_ROTATIONS[revDir][indexLeadingDigit] 183 } 184 185 if !(pentagonRotations >= 0) { 186 panic(fmt.Errorf("pentagonRotations should be large than 0")) 187 } 188 189 if !(directionRotations >= 0) { 190 panic(fmt.Errorf("directionRotations should be large than 0")) 191 } 192 193 for i := 0; i < pentagonRotations; i++ { 194 _ijkRotate60cw(&indexFijk.coord) 195 } 196 197 offset := CoordIJK{} 198 _neighbor(&offset, dir) 199 200 // Scale offset based on resolution 201 for r := res - 1; r >= 0; r-- { 202 if isResClassIII(r + 1) { 203 // rotate ccw 204 _downAp7(&offset) 205 } else { 206 // rotate cw 207 _downAp7r(&offset) 208 } 209 } 210 211 for i := 0; i < directionRotations; i++ { 212 _ijkRotate60cw(&offset) 213 } 214 215 // Perform necessary translation 216 _ijkAdd(&indexFijk.coord, &offset, &indexFijk.coord) 217 _ijkNormalize(&indexFijk.coord) 218 } else if originOnPent && indexOnPent { 219 // If the origin and index are on pentagon, and we checked that the base 220 // cells are the same or neighboring, then they must be the same base 221 // cell. 222 if !(baseCell == originBaseCell) { 223 panic(fmt.Sprintf("must be same base cell")) 224 } 225 226 originLeadingDigit := _h3LeadingNonZeroDigit(origin) 227 indexLeadingDigit := _h3LeadingNonZeroDigit(h3) 228 if FAILED_DIRECTIONS[originLeadingDigit][indexLeadingDigit] { 229 // TODO: We may be unfolding the pentagon incorrectly in this case; 230 // return an error code until this is guaranteed to be correct. 231 return 5 232 } 233 234 withinPentagonRotations := PENTAGON_ROTATIONS[originLeadingDigit][indexLeadingDigit] 235 for i := 0; i < withinPentagonRotations; i++ { 236 _ijkRotate60cw(&indexFijk.coord) 237 } 238 } 239 240 *out = indexFijk.coord 241 return 0 242 } 243 244 /** 245 * Produces an index for ijk+ coordinates anchored by an origin. 246 * 247 * The coordinate space used by this function may have deleted 248 * regions or warping due to pentagonal distortion. 249 * 250 * Failure may occur if the coordinates are too far away from the origin 251 * or if the index is on the other side of a pentagon. 252 * 253 * @param origin An anchoring index for the ijk+ coordinate system. 254 * @param ijk IJK+ Coordinates to find the index of 255 * @param out The index will be placed here on success 256 * @return 0 on success, or another value on failure. 257 */ 258 func localIjkToH3(origin H3Index, ijk *CoordIJK, out *H3Index) int { 259 res := H3_GET_RESOLUTION(origin) 260 originBaseCell := H3_GET_BASE_CELL(origin) 261 originOnPent := _isBaseCellPentagon(originBaseCell) 262 263 // This logic is very similar to faceIjkToH3 264 // initialize the index 265 *out = H3_INIT 266 H3_SET_MODE(out, H3_HEXAGON_MODE) 267 H3_SET_RESOLUTION(out, res) 268 269 // check for res 0/base cell 270 if res == 0 { 271 if ijk.i > 1 || ijk.j > 1 || ijk.k > 1 { 272 // out of range input 273 return 1 274 } 275 276 dir := _unitIjkToDigit(ijk) 277 newBaseCell := _getBaseCellNeighbor(originBaseCell, dir) 278 if newBaseCell == INVALID_BASE_CELL { 279 // Moving in an invalid direction off a pentagon. 280 return 1 281 } 282 H3_SET_BASE_CELL(out, newBaseCell) 283 return 0 284 } 285 286 // we need to find the correct base cell offset (if any) for this H3 index; 287 // start with the passed in base cell and resolution res ijk coordinates 288 // in that base cell's coordinate system 289 ijkCopy := *ijk 290 291 // build the from H3Index finest res up 292 // adjust r for the fact that the res 0 base cell offsets the indexing 293 // digits 294 for r := res - 1; r >= 0; r-- { 295 lastIJK := &CoordIJK{} 296 var lastCenter CoordIJK 297 if isResClassIII(r + 1) { 298 // rotate ccw 299 _upAp7(&ijkCopy) 300 lastCenter = ijkCopy 301 _downAp7(&lastCenter) 302 } else { 303 // rotate cw 304 _upAp7r(&ijkCopy) 305 lastCenter = ijkCopy 306 _downAp7r(&lastCenter) 307 } 308 309 var diff CoordIJK 310 _ijkSub(lastIJK, &lastCenter, &diff) 311 _ijkNormalize(&diff) 312 H3_SET_INDEX_DIGIT(out, r+1, _unitIjkToDigit(&diff)) 313 } 314 315 // ijkCopy should now hold the IJK of the base cell in the 316 // coordinate system of the current base cell 317 318 if ijkCopy.i > 1 || ijkCopy.j > 1 || ijkCopy.k > 1 { 319 // out of range input 320 return 2 321 } 322 323 // lookup the correct base cell 324 dir := _unitIjkToDigit(&ijkCopy) 325 baseCell := _getBaseCellNeighbor(originBaseCell, dir) 326 // If baseCell is invalid, it must be because the origin base cell is a 327 // pentagon, and because pentagon base cells do not border each other, 328 // baseCell must not be a pentagon. 329 indexOnPent := false 330 if baseCell != INVALID_BASE_CELL { 331 indexOnPent = _isBaseCellPentagon(baseCell) 332 } 333 334 if dir != CENTER_DIGIT { 335 // If the index is in a warped direction, we need to unwarp the base 336 // cell direction. There may be further need to rotate the index digits. 337 pentagonRotations := 0 338 if originOnPent { 339 originLeadingDigit := _h3LeadingNonZeroDigit(origin) 340 pentagonRotations = PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][dir] 341 for i := 0; i < pentagonRotations; i++ { 342 dir = _rotate60ccw(dir) 343 } 344 // The pentagon rotations are being chosen so that dir is not the 345 // deleted direction. If it still happens, it means we're moving 346 // into a deleted subsequence, so there is no index here. 347 if dir == K_AXES_DIGIT { 348 return 3 349 } 350 baseCell = _getBaseCellNeighbor(originBaseCell, dir) 351 352 // indexOnPent does not need to be checked again since no pentagon 353 // base cells border each other. 354 //assert(baseCell != INVALID_BASE_CELL); 355 //assert(!_isBaseCellPentagon(baseCell)); 356 } 357 358 // Now we can determine the relation between the origin and target base 359 // cell. 360 baseCellRotations := baseCellNeighbor60CCWRots[originBaseCell][dir] 361 //assert(baseCellRotations >= 0); 362 363 // Adjust for pentagon warping within the base cell. The base cell 364 // should be in the right location, so now we need to rotate the index 365 // back. We might not need to check for errors since we would just be 366 // double mapping. 367 if indexOnPent { 368 revDir := _getBaseCellDirection(baseCell, originBaseCell) 369 //assert(revDir != INVALID_DIGIT); 370 371 // Adjust for the different coordinate space in the two base cells. 372 // This is done first because we need to do the pentagon rotations 373 // based on the leading digit in the pentagon's coordinate system. 374 for i := 0; i < baseCellRotations; i++ { 375 *out = _h3Rotate60ccw(*out) 376 } 377 378 indexLeadingDigit := _h3LeadingNonZeroDigit(*out) 379 380 if _isBaseCellPolarPentagon(baseCell) { 381 pentagonRotations = PENTAGON_ROTATIONS_REVERSE_POLAR[revDir][indexLeadingDigit] 382 } else { 383 pentagonRotations = PENTAGON_ROTATIONS_REVERSE_NONPOLAR[revDir][indexLeadingDigit] 384 } 385 386 //assert(pentagonRotations >= 0); 387 for i := 0; i < pentagonRotations; i++ { 388 *out = _h3RotatePent60ccw(*out) 389 } 390 } else { 391 //assert(pentagonRotations >= 0); 392 for i := 0; i < pentagonRotations; i++ { 393 *out = _h3Rotate60ccw(*out) 394 } 395 396 // Adjust for the different coordinate space in the two base cells. 397 for i := 0; i < baseCellRotations; i++ { 398 *out = _h3Rotate60ccw(*out) 399 } 400 } 401 } else if originOnPent && indexOnPent { 402 originLeadingDigit := _h3LeadingNonZeroDigit(origin) 403 indexLeadingDigit := _h3LeadingNonZeroDigit(*out) 404 withinPentagonRotations := PENTAGON_ROTATIONS_REVERSE[originLeadingDigit][indexLeadingDigit] 405 //assert(withinPentagonRotations >= 0); 406 407 for i := 0; i < withinPentagonRotations; i++ { 408 *out = _h3Rotate60ccw(*out) 409 } 410 } 411 412 if indexOnPent { 413 // TODO: There are cases in h3ToLocalIjk which are failed but not 414 // accounted for here - instead just fail if the recovered index is 415 // invalid. 416 if _h3LeadingNonZeroDigit(*out) == K_AXES_DIGIT { 417 return 4 418 } 419 } 420 421 H3_SET_BASE_CELL(out, baseCell) 422 return 0 423 } 424 425 /** 426 * Produces ij coordinates for an index anchored by an origin. 427 * 428 * The coordinate space used by this function may have deleted 429 * regions or warping due to pentagonal distortion. 430 * 431 * Coordinates are only comparable if they come from the same 432 * origin index. 433 * 434 * Failure may occur if the index is too far away from the origin 435 * or if the index is on the other side of a pentagon. 436 * 437 * This function is experimental, and its output is not guaranteed 438 * to be compatible across different versions of H3. 439 * 440 * @param origin An anchoring index for the ij coordinate system. 441 * @param index Index to find the coordinates of 442 * @param out ij coordinates of the index will be placed here on success 443 * @return 0 on success, or another value on failure. 444 */ 445 func experimentalH3ToLocalIj(origin H3Index, h3 H3Index, out *CoordIJ) int { 446 // This function is currently experimental. Once ready to be part of the 447 // non-experimental API, this function (with the experimental prefix) will 448 // be marked as deprecated and to be removed in the next major version. It 449 // will be replaced with a non-prefixed function name. 450 var ijk CoordIJK 451 failed := h3ToLocalIjk(origin, h3, &ijk) 452 453 if failed != 0 { 454 return failed 455 } 456 457 ijkToIj(&ijk, out) 458 459 return 0 460 } 461 462 /** 463 * Produces an index for ij coordinates anchored by an origin. 464 * 465 * The coordinate space used by this function may have deleted 466 * regions or warping due to pentagonal distortion. 467 * 468 * Failure may occur if the index is too far away from the origin 469 * or if the index is on the other side of a pentagon. 470 * 471 * This function is experimental, and its output is not guaranteed 472 * to be compatible across different versions of H3. 473 * 474 * @param origin An anchoring index for the ij coordinate system. 475 * @param out ij coordinates to index. 476 * @param index Index will be placed here on success. 477 * @return 0 on success, or another value on failure. 478 */ 479 func experimentalLocalIjToH3(origin H3Index, ij *CoordIJ, out *H3Index) int { 480 // This function is currently experimental. Once ready to be part of the 481 // non-experimental API, this function (with the experimental prefix) will 482 // be marked as deprecated and to be removed in the next major version. It 483 // will be replaced with a non-prefixed function name. 484 var ijk CoordIJK 485 ijToIjk(ij, &ijk) 486 return localIjkToH3(origin, &ijk, out) 487 } 488 489 /** 490 * Produces the grid distance between the two indexes. 491 * 492 * This function may fail to find the distance between two indexes, for 493 * example if they are very far apart. It may also fail when finding 494 * distances for indexes on opposite sides of a pentagon. 495 * 496 * @param origin Index to find the distance from. 497 * @param index Index to find the distance to. 498 * @return The distance, or a negative number if the library could not 499 * compute the distance. 500 */ 501 func h3Distance(origin H3Index, h3 H3Index) int { 502 var originIjk, h3Ijk CoordIJK 503 if h3ToLocalIjk(origin, origin, &originIjk) != 0 { 504 // Currently there are no tests that would cause getting the coordinates 505 // for an index the same as the origin to fail. 506 return -1 // LCOV_EXCL_LINE 507 } 508 509 if h3ToLocalIjk(origin, h3, &h3Ijk) != 0 { 510 return -1 511 } 512 513 return int(ijkDistance(&originIjk, &h3Ijk)) 514 } 515 516 /** 517 * Number of indexes in a line from the start index to the end index, 518 * to be used for allocating memory. Returns a negative number if the 519 * line cannot be computed. 520 * 521 * @param start Start index of the line 522 * @param end End index of the line 523 * @return Size of the line, or a negative number if the line cannot 524 * be computed. 525 */ 526 func h3LineSize(start H3Index, end H3Index) int { 527 distance := h3Distance(start, end) 528 if distance >= 0 { 529 return distance + 1 530 } 531 return distance 532 } 533 534 /** 535 * Given cube coords as doubles, round to valid integer coordinates. Algorithm 536 * from https://www.redblobgames.com/grids/hexagons/#rounding 537 * @param i Floating-point I coord 538 * @param j Floating-point J coord 539 * @param k Floating-point K coord 540 * @param ijk IJK coord struct, modified in place 541 */ 542 func cubeRound(i, j, k float64, ijk *CoordIJK) { 543 ri := math.Round(i) 544 rj := math.Round(j) 545 rk := math.Round(k) 546 iDiff := math.Abs(ri - i) 547 jDiff := math.Abs(rj - j) 548 kDiff := math.Abs(rk - k) 549 550 // Round, maintaining valid cube coords 551 if iDiff > jDiff && iDiff > kDiff { 552 ri = -rj - rk 553 } else if jDiff > kDiff { 554 rj = -ri - rk 555 } else { 556 rk = -ri - rj 557 } 558 559 ijk.i = int(ri) 560 ijk.j = int(rj) 561 ijk.k = int(rk) 562 } 563 564 /** 565 * Given two H3 indexes, return the line of indexes between them (inclusive). 566 * 567 * This function may fail to find the line between two indexes, for 568 * example if they are very far apart. It may also fail when finding 569 * distances for indexes on opposite sides of a pentagon. 570 * 571 * Notes: 572 * 573 * - The specific output of this function should not be considered stable 574 * across library versions. The only guarantees the library provides are 575 * that the line length will be `h3Distance(start, end) + 1` and that 576 * every index in the line will be a neighbor of the preceding index. 577 * - Lines are drawn in grid space, and may not correspond exactly to either 578 * Cartesian lines or great arcs. 579 * 580 * @param start Start index of the line 581 * @param end End index of the line 582 * @param out Output array, which must be of size h3LineSize(start, end) 583 * @return 0 on success, or another value on failure. 584 */ 585 func h3Line(start H3Index, end H3Index, out []H3Index) int { 586 distance := h3Distance(start, end) 587 // Early exit if we can't calculate the line 588 if distance < 0 { 589 return distance 590 } 591 592 // Get IJK coords for the start and end. We've already confirmed 593 // that these can be calculated with the distance check above. 594 startIjk := &CoordIJK{} 595 endIjk := &CoordIJK{} 596 597 // Convert H3 addresses to IJK coords 598 h3ToLocalIjk(start, start, startIjk) 599 h3ToLocalIjk(start, end, endIjk) 600 601 // Convert IJK to cube coordinates suitable for linear interpolation 602 ijkToCube(startIjk) 603 ijkToCube(endIjk) 604 iStep := func() float64 { 605 if distance != 0 { 606 return float64(endIjk.i-startIjk.i) / float64(distance) 607 } 608 return 0 609 }() 610 jStep := func() float64 { 611 if distance != 0 { 612 return float64(endIjk.j-startIjk.j) / float64(distance) 613 } 614 return 0 615 }() 616 617 kStep := func() float64 { 618 if distance != 0 { 619 return float64(endIjk.k-startIjk.k) / float64(distance) 620 } 621 return 0 622 }() 623 624 currentIjk := &CoordIJK{startIjk.i, startIjk.j, startIjk.k} 625 626 for n := 0; n <= distance; n++ { 627 cubeRound(float64(startIjk.i)+iStep*float64(n), float64(startIjk.j)+jStep*float64(n), float64(startIjk.k)+kStep*float64(n), currentIjk) 628 // Convert cube . ijk . h3 index 629 cubeToIjk(currentIjk) 630 localIjkToH3(start, currentIjk, &out[n]) 631 } 632 633 return 0 634 }