github.com/liucxer/courier@v1.7.1/h3/algos.go (about) 1 package h3 2 3 const HEX_RANGE_SUCCESS = 0 4 const HEX_RANGE_PENTAGON = 1 5 const HEX_RANGE_K_SUBSEQUENCE = 2 6 const MAX_ONE_RING_SIZE = 7 7 8 /** 9 * Directions used for traversing a hexagonal ring counterclockwise around 10 * {1, 0, 0} 11 * 12 * <pre> 13 * _ 14 * _/ \\_ 15 * / \\5/ \\ 16 * \\0/ \\4/ 17 * / \\_/ \\ 18 * \\1/ \\3/ 19 * \\2/ 20 * </pre> 21 */ 22 var DIRECTIONS = [6]Direction{J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT} 23 24 /** 25 * Direction used for traversing to the next outward hexagonal ring. 26 */ 27 const NEXT_RING_DIRECTION = I_AXES_DIGIT 28 29 /** 30 * New digit when traversing along class II grids. 31 * 32 * Current digit . direction . new digit. 33 */ 34 var NEW_DIGIT_II = [7][7]Direction{ 35 {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT}, 36 {K_AXES_DIGIT, I_AXES_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, IK_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT}, 37 {J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT}, 38 {JK_AXES_DIGIT, IJ_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT}, 39 {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT}, 40 {IK_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, I_AXES_DIGIT}, 41 {IJ_AXES_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, J_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT, JK_AXES_DIGIT}, 42 } 43 44 /** 45 * New traversal direction when traversing along class II grids. 46 * 47 * Current digit . direction . new ap7 move (at coarser level). 48 */ 49 var NEW_ADJUSTMENT_II = [7][7]Direction{ 50 {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT}, 51 {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT}, 52 {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT}, 53 {CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT}, 54 {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT}, 55 {CENTER_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT}, 56 {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT}, 57 } 58 59 /** 60 * New traversal direction when traversing along class III grids. 61 * 62 * Current digit . direction . new ap7 move (at coarser level). 63 */ 64 var NEW_DIGIT_III = [7][7]Direction{ 65 {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT}, 66 {K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT}, 67 {J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT}, 68 {JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT}, 69 {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT}, 70 {IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT}, 71 {IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT}, 72 } 73 74 /** 75 * New traversal direction when traversing along class III grids. 76 * 77 * Current digit . direction . new ap7 move (at coarser level). 78 */ 79 var NEW_ADJUSTMENT_III = [7][7]Direction{ 80 {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT}, 81 {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT}, 82 {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT}, 83 {CENTER_DIGIT, JK_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT}, 84 {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, I_AXES_DIGIT}, 85 {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT}, 86 {CENTER_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, I_AXES_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT}, 87 } 88 89 /** 90 * Maximum number of indices that result from the kRing algorithm with the given 91 * k. Formula source and proof: https://oeis.org/A003215 92 * 93 * @param k k value, k >= 0. 94 */ 95 func maxKringSize(k int) int { return 3*k*(k+1) + 1 } 96 97 /** 98 * k-rings produces indices within k distance of the origin index. 99 * 100 * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and 101 * all neighboring indices, and so on. 102 * 103 * Output is placed in the provided array in no particular order. Elements of 104 * the output array may be left zero, as can happen when crossing a pentagon. 105 * 106 * @param origin Origin location. 107 * @param k k >= 0 108 * @param out Zero-filled array which must be of size maxKringSize(k). 109 */ 110 func kRing(origin H3Index, k int, out []H3Index) { 111 maxIdx := maxKringSize(k) 112 distances := make([]int, maxIdx) 113 kRingDistances(origin, k, out, distances) 114 } 115 116 /** 117 * k-rings produces indices within k distance of the origin index. 118 * 119 * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and 120 * all neighboring indices, and so on. 121 * 122 * Output is placed in the provided array in no particular order. Elements of 123 * the output array may be left zero, as can happen when crossing a pentagon. 124 * 125 * @param origin Origin location. 126 * @param k k >= 0 127 * @param out Zero-filled array which must be of size maxKringSize(k). 128 * @param distances Zero-filled array which must be of size maxKringSize(k). 129 */ 130 func kRingDistances(origin H3Index, k int, out []H3Index, distances []int) { 131 maxIdx := maxKringSize(k) 132 // Optimistically try the faster hexRange algorithm first 133 failed := hexRangeDistances(origin, k, out, distances) 134 if failed != 0 { 135 // Fast algo failed, fall back to slower, correct algo 136 // and also wipe out array because contents untrustworthy 137 out = make([]H3Index, maxIdx) // todo 138 139 _kRingInternal(origin, k, out, distances, maxIdx, 0) 140 } 141 } 142 143 /** 144 * Internal helper function called recursively for kRingDistances. 145 * 146 * Adds the origin index to the output set (treating it as a hash set) 147 * and recurses to its neighbors, if needed. 148 * 149 * @param origin 150 * @param k Maximum distance to move from the origin. 151 * @param out Array treated as a hash set, elements being either or H3Index 0. 152 * @param distances Scratch area, with elements paralleling the out array. 153 * Elements indicate ijk distance from the origin index to the output index. 154 * @param maxIdx Size of out and scratch arrays (must be maxKringSize(k)) 155 * @param curK Current distance from the origin. 156 */ 157 func _kRingInternal(origin H3Index, k int, out []H3Index, distances []int, maxIdx int, curK int) { 158 if origin == 0 { 159 return 160 } 161 162 // Put origin in the output array. out is used as a hash set. 163 off := uint64(origin) % uint64(maxIdx) 164 for out[off] != 0 && out[off] != origin { 165 off = (off + 1) % uint64(maxIdx) 166 } 167 168 // We either got a free slot in the hash set or hit a duplicate 169 // We might need to process the duplicate anyways because we got 170 // here on a longer path before. 171 if out[off] == origin && distances[off] <= curK { 172 return 173 } 174 out[off] = origin 175 distances[off] = curK 176 177 // Base case: reached an index k away from the origin. 178 if curK >= k { 179 return 180 } 181 182 // Recurse to all neighbors in no particular order. 183 for i := 0; i < 6; i++ { 184 rotations := 0 185 _kRingInternal(h3NeighborRotations(origin, DIRECTIONS[i], &rotations), k, out, distances, maxIdx, curK+1) 186 } 187 } 188 189 /** 190 * Returns the hexagon index neighboring the origin, in the direction dir. 191 * 192 * Implementation note: The only reachable case where this returns 0 is if the 193 * origin is a pentagon and the translation is in the k direction. Thus, 194 * 0 can only be returned if origin is a pentagon. 195 * 196 * @param origin Origin index 197 * @param dir Direction to move in 198 * @param rotations Number of ccw rotations to perform to reorient the 199 * translation vector. Will be modified to the new number of 200 * rotations to perform (such as when crossing a face edge.) 201 * @return of H3Index the specified neighbor or 0 if deleted k-subsequence 202 * distortion is encountered. 203 */ 204 func h3NeighborRotations(origin H3Index, dir Direction, rotations *int) H3Index { 205 out := origin 206 for i := 0; i < *rotations; i++ { 207 dir = _rotate60ccw(dir) 208 } 209 210 newRotations := 0 211 oldBaseCell := H3_GET_BASE_CELL(out) 212 oldLeadingDigit := _h3LeadingNonZeroDigit(out) 213 214 // Adjust the indexing digits and, if needed, the base cell. 215 r := H3_GET_RESOLUTION(out) - 1 216 for { 217 if r == -1 { 218 H3_SET_BASE_CELL(&out, baseCellNeighbors[oldBaseCell][dir]) 219 newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir] 220 if H3_GET_BASE_CELL(out) == INVALID_BASE_CELL { 221 // Adjust for the deleted k vertex at the base cell level. 222 // This edge actually borders a different neighbor. 223 H3_SET_BASE_CELL(&out, baseCellNeighbors[oldBaseCell][IK_AXES_DIGIT]) 224 newRotations = 225 baseCellNeighbor60CCWRots[oldBaseCell][IK_AXES_DIGIT] 226 227 // perform the adjustment for the k-subsequence we're skipping 228 // over. 229 out = _h3Rotate60ccw(out) 230 *rotations = *rotations + 1 231 } 232 233 break 234 } else { 235 oldDigit := H3_GET_INDEX_DIGIT(out, r+1) 236 var nextDir Direction 237 if isResClassIII(r + 1) { 238 H3_SET_INDEX_DIGIT(&out, r+1, NEW_DIGIT_II[oldDigit][dir]) 239 nextDir = NEW_ADJUSTMENT_II[oldDigit][dir] 240 } else { 241 H3_SET_INDEX_DIGIT(&out, r+1, NEW_DIGIT_III[oldDigit][dir]) 242 nextDir = NEW_ADJUSTMENT_III[oldDigit][dir] 243 } 244 245 if nextDir != CENTER_DIGIT { 246 dir = nextDir 247 r-- 248 } else { 249 // No more adjustment to perform 250 break 251 } 252 } 253 } 254 255 newBaseCell := H3_GET_BASE_CELL(out) 256 if _isBaseCellPentagon(newBaseCell) { 257 alreadyAdjustedKSubsequence := 0 258 259 // force rotation out of missing k-axes sub-sequence 260 if _h3LeadingNonZeroDigit(out) == K_AXES_DIGIT { 261 if oldBaseCell != newBaseCell { 262 // in this case, we traversed into the deleted 263 // k subsequence of a pentagon base cell. 264 // We need to rotate out of that case depending 265 // on how we got here. 266 // check for a cw/ccw var face offset; default is ccw 267 268 if _baseCellIsCwOffset( 269 newBaseCell, baseCellData[oldBaseCell].homeFijk.face) { 270 out = _h3Rotate60cw(out) 271 } else { 272 // See cwOffsetPent in testKRing.c for why this is 273 // unreachable. 274 out = _h3Rotate60ccw(out) // LCOV_EXCL_LINE 275 } 276 alreadyAdjustedKSubsequence = 1 277 } else { 278 // In this case, we traversed into the deleted 279 // k subsequence from within the same pentagon 280 // base cell. 281 if oldLeadingDigit == CENTER_DIGIT { 282 // Undefined: the k direction is deleted from here 283 return H3_INVALID_INDEX 284 } else if oldLeadingDigit == JK_AXES_DIGIT { 285 // Rotate out of the deleted k subsequence 286 // We also need an additional change to the direction we're 287 // moving in 288 out = _h3Rotate60ccw(out) 289 *rotations = *rotations + 1 290 } else if oldLeadingDigit == IK_AXES_DIGIT { 291 // Rotate out of the deleted k subsequence 292 // We also need an additional change to the direction we're 293 // moving in 294 out = _h3Rotate60cw(out) 295 *rotations = *rotations + 5 296 } else { 297 // Should never occur 298 return H3_INVALID_INDEX // LCOV_EXCL_LINE 299 } 300 } 301 } 302 303 for i := 0; i < newRotations; i++ { 304 out = _h3RotatePent60ccw(out) 305 } 306 307 // Account for differing orientation of the base cells (this edge 308 // might not follow properties of some other edges.) 309 if oldBaseCell != newBaseCell { 310 if _isBaseCellPolarPentagon(newBaseCell) { 311 // 'polar' base cells behave differently because they have all 312 // i neighbors. 313 if oldBaseCell != 118 && oldBaseCell != 8 && 314 _h3LeadingNonZeroDigit(out) != JK_AXES_DIGIT { 315 *rotations = *rotations + 1 316 } 317 } else if _h3LeadingNonZeroDigit(out) == IK_AXES_DIGIT && alreadyAdjustedKSubsequence == 0 { 318 // account for distortion introduced to the 5 neighbor by the 319 // deleted k subsequence. 320 *rotations = *rotations + 1 321 } 322 } 323 } else { 324 for i := 0; i < newRotations; i++ { 325 out = _h3Rotate60ccw(out) 326 } 327 } 328 329 *rotations = (*rotations + newRotations) % 6 330 return out 331 } 332 333 /** 334 * hexRange produces indexes within k distance of the origin index. 335 * Output behavior is undefined when one of the indexes returned by this 336 * function is a pentagon or is in the pentagon distortion area. 337 * 338 * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and 339 * all neighboring indexes, and so on. 340 * 341 * Output is placed in the provided array in order of increasing distance from 342 * the origin. 343 * 344 * @param origin Origin location. 345 * @param k k >= 0 346 * @param out Array which must be of size maxKringSize(k). 347 * @return 0 if no pentagon or pentagonal distortion area was encountered. 348 */ 349 func hexRange(origin H3Index, k int, out []H3Index) int { 350 return hexRangeDistances(origin, k, out, []int{}) 351 } 352 353 /** 354 * hexRange produces indexes within k distance of the origin index. 355 * Output behavior is undefined when one of the indexes returned by this 356 * function is a pentagon or is in the pentagon distortion area. 357 * 358 * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and 359 * all neighboring indexes, and so on. 360 * 361 * Output is placed in the provided array in order of increasing distance from 362 * the origin. The distances in hexagons is placed in the distances array at 363 * the same offset. 364 * 365 * @param origin Origin location. 366 * @param k k >= 0 367 * @param out Array which must be of size maxKringSize(k). 368 * @param distances Null or array which must be of size maxKringSize(k). 369 * @return 0 if no pentagon or pentagonal distortion area was encountered. 370 */ 371 func hexRangeDistances(origin H3Index, k int, out []H3Index, distances []int) int { 372 // Return codes: 373 // 1 Pentagon was encountered 374 // 2 Pentagon distortion (deleted k subsequence) was encountered 375 // Pentagon being encountered is not itself var problem a; really the deleted 376 // k-subsequence is the problem, but for compatibility reasons we fail on 377 // the pentagon. 378 379 // k must be >= 0, so origin is always needed 380 idx := 0 381 out[idx] = origin 382 if len(distances) > idx { 383 distances[idx] = 0 384 } 385 idx++ 386 if h3IsPentagon(origin) { 387 // Pentagon var encountered was; bail out as user doesn't want this. 388 return HEX_RANGE_PENTAGON 389 } 390 391 // 0 < ring <= k, current ring 392 ring := 1 393 // 0 <= direction < 6, current side of the ring 394 direction := 0 395 // 0 <= i < ring, current position on the side of the ring 396 i := 0 397 // Number of 60 degree ccw rotations to perform on the direction (based on 398 // which faces have been crossed.) 399 rotations := 0 400 for ring <= k { 401 if direction == 0 && i == 0 { 402 // Not putting in the output set as it will be done later, at 403 // the end of this ring. 404 origin = 405 h3NeighborRotations(origin, NEXT_RING_DIRECTION, &rotations) 406 if origin == 0 { // LCOV_EXCL_BR_LINE 407 // Should not be possible because `origin` would have to be a 408 // pentagon 409 return HEX_RANGE_K_SUBSEQUENCE // LCOV_EXCL_LINE 410 } 411 412 if h3IsPentagon(origin) { 413 // Pentagon var encountered was; bail out as user doesn't want this. 414 return HEX_RANGE_PENTAGON 415 } 416 } 417 418 origin = h3NeighborRotations(origin, DIRECTIONS[direction], &rotations) 419 if origin == 0 { // LCOV_EXCL_BR_LINE 420 // Should not be possible because `origin` would have to be a 421 // pentagon 422 return HEX_RANGE_K_SUBSEQUENCE // LCOV_EXCL_LINE 423 } 424 out[idx] = origin 425 if len(distances) > idx { 426 distances[idx] = ring 427 } 428 idx++ 429 i++ 430 // Check if end of this side of the k-ring 431 if i == ring { 432 i = 0 433 direction++ 434 // Check if end of this ring. 435 if direction == 6 { 436 direction = 0 437 ring++ 438 } 439 } 440 441 if h3IsPentagon(origin) { 442 // Pentagon var encountered was; bail out as user doesn't want this. 443 return HEX_RANGE_PENTAGON 444 } 445 } 446 return HEX_RANGE_SUCCESS 447 } 448 449 /** 450 * hexRanges takes an array of input hex IDs and a max k-ring and returns an 451 * array of hexagon IDs sorted first by the original hex IDs and then by the 452 * k-ring (0 to max), with no guaranteed sorting within each k-ring group. 453 * 454 * @param h3Set A pointer to an array of H3Indexes 455 * @param length The total number of H3Indexes in h3Set 456 * @param k The number of rings to generate 457 * @param out A pointer to the output memory to dump the new set of H3Indexes to 458 * The memory block should be equal to maxKringSize(k) * length 459 * @return 0 if no pentagon is encountered. Cannot trust output otherwise 460 */ 461 func hexRanges(h3Set []H3Index, length int, k int, out []H3Index) int { 462 success := 0 463 var segment []H3Index 464 segmentSize := maxKringSize(k) 465 for i := 0; i < length; i++ { 466 // Determine the appropriate segment of the output array to operate on 467 468 //segment = out + i*segmentSize; 469 segment = append(out, make([]H3Index, i*segmentSize)...) 470 471 success = hexRange(h3Set[i], k, segment) 472 if success != 0 { 473 return success 474 } 475 } 476 return 0 477 } 478 479 /** 480 * Returns the "hollow" ring of hexagons at exactly grid distance k from 481 * the origin hexagon. In particular, k=0 returns just the origin hexagon. 482 * 483 * A nonzero failure code may be returned in some cases, for example, 484 * if a pentagon is encountered. 485 * Failure cases may be fixed in future versions. 486 * 487 * @param origin Origin location. 488 * @param k k >= 0 489 * @param out Array which must be of size 6 * k (or 1 if k == 0) 490 * @return 0 var successful if; nonzero otherwise. 491 */ 492 func hexRing(origin H3Index, k int, out []H3Index) int { 493 // Short-circuit on 'identity' ring 494 if k == 0 { 495 out[0] = origin 496 return 0 497 } 498 idx := 0 499 // Number of 60 degree ccw rotations to perform on the direction (based on 500 // which faces have been crossed.) 501 rotations := 0 502 // Scratch structure for checking for pentagons 503 if h3IsPentagon(origin) { 504 // Pentagon var encountered was; bail out as user doesn't want this. 505 return HEX_RANGE_PENTAGON 506 } 507 508 for ring := 0; ring < k; ring++ { 509 origin = h3NeighborRotations(origin, NEXT_RING_DIRECTION, &rotations) 510 if origin == 0 { // LCOV_EXCL_BR_LINE 511 // Should not be possible because `origin` would have to be a 512 // pentagon 513 return HEX_RANGE_K_SUBSEQUENCE // LCOV_EXCL_LINE 514 } 515 516 if h3IsPentagon(origin) { 517 return HEX_RANGE_PENTAGON 518 } 519 } 520 521 lastIndex := origin 522 out[idx] = origin 523 idx++ 524 for direction := 0; direction < 6; direction++ { 525 for pos := 0; pos < k; pos++ { 526 origin = h3NeighborRotations(origin, DIRECTIONS[direction], &rotations) 527 if origin == 0 { // LCOV_EXCL_BR_LINE 528 // Should not be possible because `origin` would have to be a 529 // pentagon 530 return HEX_RANGE_K_SUBSEQUENCE // LCOV_EXCL_LINE 531 } 532 533 // Skip the very last index, it was already added. We do 534 // however need to traverse to it because of the pentagonal 535 // distortion check, below. 536 if pos != k-1 || direction != 5 { 537 out[idx] = origin 538 idx++ 539 if h3IsPentagon(origin) { 540 return HEX_RANGE_PENTAGON 541 } 542 } 543 } 544 } 545 546 // Check that this matches the expected lastIndex, if it doesn't, 547 // it indicates pentagonal distortion occurred and we should report 548 // failure. 549 if lastIndex != origin { 550 return HEX_RANGE_PENTAGON 551 } 552 return HEX_RANGE_SUCCESS 553 } 554 555 /** 556 * maxPolyfillSize returns the number of hexagons to allocate space for when 557 * performing a polyfill on the given GeoJSON-like data structure. 558 * 559 * The size is the maximum of either the number of points in the geofence or the 560 * number of hexagons in the bounding box of the geofence. 561 * 562 * @param geoPolygon A GeoJSON-like data structure indicating the poly to fill 563 * @param res Hexagon resolution (0-15) 564 * @return number of hexagons to allocate for 565 */ 566 func maxPolyfillSize(geoPolygon *GeoPolygon, res int) int { 567 // Get the bounding box for the GeoJSON-like struct 568 var bbox BBox 569 geofence := geoPolygon.geofence 570 bboxFrom(&geofence, &bbox) 571 numHexagons := bboxHexEstimate(&bbox, res) 572 // This algorithm assumes that the number of vertices is usually less than 573 // the number of hexagons, but when it's wrong, this will keep it from 574 // failing 575 totalVerts := geofence.numVerts 576 for i := 0; i < geoPolygon.numHoles; i++ { 577 totalVerts += geoPolygon.holes[i].numVerts 578 } 579 if numHexagons < totalVerts { 580 numHexagons = totalVerts 581 } 582 return numHexagons 583 } 584 585 /** 586 * polyfill takes a given GeoJSON-like data structure and preallocated, 587 * zeroed memory, and fills it with the hexagons that are contained by 588 * the GeoJSON-like data structure. 589 * 590 * This implementation traces the GeoJSON geofence(s) in cartesian space with 591 * hexagons, tests them and their neighbors to be contained by the geofence(s), 592 * and then any newly found hexagons are used to test again until no new 593 * hexagons are found. 594 * 595 * @param geoPolygon The geofence and holes defining the relevant area 596 * @param res The Hexagon resolution (0-15) 597 * @param out The slab of zeroed memory to write to. Assumed to be big enough. 598 */ 599 func polyfill(geoPolygon *GeoPolygon, res int, out []H3Index) { 600 // TODO: Eliminate this wrapper with the H3 4.0.0 release 601 failure := _polyfillInternal(geoPolygon, res, out) 602 // The polyfill algorithm can theoretically fail if the allocated memory is 603 // not large enough for the polygon, but this should be impossible given the 604 // conservative overestimation of the number of hexagons possible. 605 // LCOV_EXCL_START 606 if failure != 0 { 607 numHexagons := maxPolyfillSize(geoPolygon, res) 608 for i := 0; i < numHexagons; i++ { 609 out[i] = H3_INVALID_INDEX 610 } 611 } 612 // LCOV_EXCL_STOP 613 } 614 615 /** 616 * _getEdgeHexagons takes a given geofence ring (either the main geofence or 617 * one of the holes) and traces it with hexagons and updates the search and 618 * found memory blocks. This is used for determining the initial hexagon set 619 * for the polyfill algorithm to execute on. 620 * 621 * @param geofence The geofence (or hole) to be traced 622 * @param numHexagons The maximum number of hexagons possible for the geofence 623 * (also the bounds of the search and found arrays) 624 * @param res The hexagon resolution (0-15) 625 * @param numSearchHexes The number of hexagons found so far to be searched 626 * @param search The block of memory containing the hexagons to search from 627 * @param found The block of memory containing the hexagons found from the 628 * search 629 * 630 * @return An error code if the hash function cannot insert a found hexagon 631 * into the found array. 632 */ 633 func _getEdgeHexagons(geofence *Geofence, numHexagons int, res int, numSearchHexes *int, search []H3Index, found []H3Index) int { 634 for i := 0; i < geofence.numVerts; i++ { 635 origin := geofence.verts[i] 636 var destination GeoCoord 637 638 if i == geofence.numVerts-1 { 639 destination = geofence.verts[0] 640 } else { 641 destination = geofence.verts[i+1] 642 } 643 644 numHexesEstimate := lineHexEstimate(&origin, &destination, res) 645 for j := 0; j < numHexesEstimate; j++ { 646 var interpolate GeoCoord 647 interpolate.Lat = (origin.Lat * float64(numHexesEstimate-j) / float64(numHexesEstimate)) + (destination.Lat * float64(j) / float64(numHexesEstimate)) 648 interpolate.Lon = (origin.Lon * float64(numHexesEstimate-j) / float64(numHexesEstimate)) + (destination.Lon * float64(j) / float64(numHexesEstimate)) 649 pointHex := geoToH3(&interpolate, res) 650 // A simple hash to store the hexagon, or move to another place if 651 // needed 652 loc := (int)(uint64(pointHex) % uint64(numHexagons)) 653 loopCount := 0 654 for found[loc] != 0 { 655 // If this conditional is reached, the `found` memory block is 656 // too small for the given polygon. This should not happen. 657 if loopCount > numHexagons { 658 return -1 659 } // LCOV_EXCL_LINE 660 if found[loc] == pointHex { 661 break // At least two points of the geofence index to the 662 } 663 // same cell 664 loc = (loc + 1) % numHexagons 665 loopCount++ 666 } 667 if found[loc] == pointHex { 668 continue // Skip this hex, already exists in the found hash 669 } 670 // Otherwise, set it in the found hash for now 671 found[loc] = pointHex 672 search[*numSearchHexes] = pointHex 673 (*numSearchHexes)++ 674 } 675 } 676 return 0 677 } 678 679 /** 680 * _polyfillInternal traces the provided geoPolygon data structure with hexagons 681 * and then iteratively searches through these hexagons and their immediate 682 * neighbors to see if they are contained within the polygon or not. Those that 683 * are found are added to the out array as well as the found array. Once all 684 * hexagons to search are checked, the found hexagons become the new search 685 * array and the found array is wiped and the process repeats until no new 686 * hexagons can be found. 687 * 688 * @param geoPolygon The geofence and holes defining the relevant area 689 * @param res The Hexagon resolution (0-15) 690 * @param out The slab of zeroed memory to write to. Assumed to be big enough. 691 * 692 * @return An error code if any of the hash operations fails to insert a hexagon 693 * into an array of memory. 694 */ 695 func _polyfillInternal(geoPolygon *GeoPolygon, res int, out []H3Index) int { 696 // One of the goals of the polyfill algorithm is that two adjacent polygons 697 // with zero overlap have zero overlapping hexagons. That the hexagons are 698 // uniquely assigned. There are a few approaches to take here, such as 699 // deciding based on which polygon has the greatest overlapping area of the 700 // hexagon, or the most number of contained points on the hexagon (using the 701 // center poas int a tiebreaker). 702 // 703 // But if the polygons are convex, both of these more complex algorithms can 704 // be reduced down to checking whether or not the center of the hexagon is 705 // contained in the polygon, and so this is the approach that this polyfill 706 // algorithm will follow, as it's simpler, faster, and the error for concave 707 // polygons is still minimal (only affecting concave shapes on the order of 708 // magnitude of the hexagon size or smaller, not impacting larger concave 709 // shapes) 710 // 711 // This first part is identical to the maxPolyfillSize above. 712 713 // Get the bounding boxes for the polygon and any holes 714 bboxes := make([]BBox, geoPolygon.numHoles+1) 715 bboxesFromGeoPolygon(geoPolygon, bboxes) 716 717 // Get the estimated number of hexagons and allocate some temporary memory 718 // for the hexagons 719 numHexagons := maxPolyfillSize(geoPolygon, res) 720 search := make([]H3Index, numHexagons) 721 found := make([]H3Index, numHexagons) 722 723 // Some metadata for tracking the state of the search and found memory 724 // blocks 725 numSearchHexes := 0 726 numFoundHexes := 0 727 728 // 1. Trace the hexagons along the polygon defining the outer geofence and 729 // add them to the search hash. The hexagon containing the geofence point 730 // may or may not be contained by the geofence (as the hexagon's center 731 // pomay int be outside of the boundary.) 732 geofence := geoPolygon.geofence 733 failure := _getEdgeHexagons(&geofence, numHexagons, res, &numSearchHexes, 734 search, found) 735 // If this branch is reached, we have exceeded the maximum number of 736 // hexagons possible and need to clean up the allocated memory. 737 // LCOV_EXCL_START 738 if failure != 0 { 739 search = nil 740 found = nil 741 bboxes = nil 742 743 return failure 744 } 745 // LCOV_EXCL_STOP 746 747 // 2. Iterate over all holes, trace the polygons defining the holes with 748 // hexagons and add to only the search hash. We're going to temporarily use 749 // the `found` hash to use for dedupe purposes and then re-zero it once 750 // we're done here, otherwise we'd have to scan the whole set on each insert 751 // to make sure there's no duplicates, which is very inefficient. 752 for i := 0; i < geoPolygon.numHoles; i++ { 753 hole := &(geoPolygon.holes[i]) 754 failure = _getEdgeHexagons(hole, numHexagons, res, &numSearchHexes, 755 search, found) 756 // If this branch is reached, we have exceeded the maximum number of 757 // hexagons possible and need to clean up the allocated memory. 758 // LCOV_EXCL_START 759 if failure != 0 { 760 search = nil 761 found = nil 762 bboxes = nil 763 764 return failure 765 } 766 // LCOV_EXCL_STOP 767 } 768 769 // 3. Re-zero the found hash so it can be used in the main loop below 770 for i := 0; i < numHexagons; i++ { 771 found[i] = 0 772 } 773 774 // 4. Begin main loop. While the search hash is not empty do the following 775 for numSearchHexes > 0 { 776 // Iterate through all hexagons in the current search hash, then loop 777 // through all neighbors and test Point-in-Poly, if point-in-poly 778 // succeeds, add to out and found hashes if not already there. 779 currentSearchNum := 0 780 i := 0 781 for currentSearchNum < numSearchHexes { 782 ring := make([]H3Index, MAX_ONE_RING_SIZE) 783 searchHex := search[i] 784 kRing(searchHex, 1, ring) 785 for j := 0; j < MAX_ONE_RING_SIZE; j++ { 786 if ring[j] == H3_INVALID_INDEX { 787 continue // Skip if this was a pentagon and only had 5 788 // neighbors 789 } 790 791 hex := ring[j] 792 793 // A simple hash to store the hexagon, or move to another place 794 // if needed. This MUST be done before the point-in-poly check 795 // since that's far more expensive 796 loc := (int)(uint64(hex) % uint64(numHexagons)) 797 loopCount := 0 798 for out[loc] != 0 { 799 // If this branch is reached, we have exceeded the maximum 800 // number of hexagons possible and need to clean up the 801 // allocated memory. 802 // LCOV_EXCL_START 803 if loopCount > numHexagons { 804 search = nil 805 found = nil 806 bboxes = nil 807 808 return -1 809 } 810 // LCOV_EXCL_STOP 811 if out[loc] == hex { 812 break // Skip duplicates found 813 } 814 loc = (loc + 1) % numHexagons 815 loopCount++ 816 } 817 if out[loc] == hex { 818 continue // Skip this hex, already exists in the out hash 819 } 820 821 // Check if the hexagon is in the polygon or not 822 var hexCenter GeoCoord 823 h3ToGeo(hex, &hexCenter) 824 825 // If not, skip 826 if !pointInsidePolygon(geoPolygon, bboxes, &hexCenter) { 827 continue 828 } 829 830 // Otherwise set it in the output array 831 out[loc] = hex 832 833 // Set the hexagon in the found hash 834 found[numFoundHexes] = hex 835 numFoundHexes++ 836 } 837 currentSearchNum++ 838 i++ 839 } 840 841 // Swap the search and found pointers, copy the found hex count to the 842 // search hex count, and zero everything related to the found memory. 843 temp := search 844 search = found 845 found = temp 846 for j := 0; j < numSearchHexes; j++ { 847 found[j] = 0 848 } 849 numSearchHexes = numFoundHexes 850 numFoundHexes = 0 851 // Repeat until no new hexagons are found 852 } 853 // The out memory structure should be complete, end it here 854 search = nil 855 found = nil 856 bboxes = nil 857 return 0 858 } 859 860 /** 861 * Internal: Create a vertex graph from a set of hexagons. It is the 862 * responsibility of the caller to call destroyon VertexGraph the populated 863 * graph, otherwise the memory in the graph nodes will not be freed. 864 * @private 865 * @param h3Set Set of hexagons 866 * @param numHexes Number of hexagons in the set 867 * @param graph Output graph 868 */ 869 func h3SetToVertexGraph(h3Set []H3Index, numHexes int, graph *VertexGraph) { 870 if numHexes < 1 { 871 // We still need to init the graph, or calls to destroywill VertexGraph 872 // fail 873 initVertexGraph(graph, 0, 0) 874 return 875 } 876 877 res := H3_GET_RESOLUTION(h3Set[0]) 878 minBuckets := 6 879 880 // TODO: Better way to calculate/guess? 881 numBuckets := 0 882 if numHexes > minBuckets { 883 numBuckets = numHexes 884 } else { 885 numBuckets = minBuckets 886 } 887 888 initVertexGraph(graph, numBuckets, res) 889 // Iterate through every hexagon 890 for i := 0; i < numHexes; i++ { 891 var vertices GeoBoundary 892 var fromVtx *GeoCoord 893 var toVtx *GeoCoord 894 var edge *VertexNode 895 896 h3ToGeoBoundary(h3Set[i], &vertices) 897 898 // iterate through every edge 899 for j := 0; j < vertices.numVerts; j++ { 900 901 fromVtx = &vertices.Verts[j] 902 toVtx = &vertices.Verts[(j+1)%vertices.numVerts] 903 // If we've seen this edge already, it will be reversed 904 edge = findNodeForEdge(graph, toVtx, fromVtx) 905 if edge != nil { 906 // If we've seen it, drop it. No edge is shared by more than 2 907 // hexagons, so we'll never see it again. 908 removeVertexNode(graph, edge) 909 } else { 910 // Add a new node for this edge 911 addVertexNode(graph, fromVtx, toVtx) 912 } 913 } 914 } 915 } 916 917 /** 918 * Internal: Create a from Linkeda GeoPolygon vertex graph. It is the 919 * responsibility of the caller to call destroyLinkedPolygon on the populated 920 * linked geo structure, or the memory for that structure will not be freed. 921 * @private 922 * @param graph Input graph 923 * @param out Output polygon 924 */ 925 func _vertexGraphToLinkedGeo(graph *VertexGraph, out *LinkedGeoPolygon) { 926 *out = LinkedGeoPolygon{} 927 var loop *LinkedGeoLoop 928 var nextVtx GeoCoord 929 930 // Find the next unused entry point 931 for edge := firstVertexNode(graph); edge != nil; edge = firstVertexNode(graph) { 932 loop = addNewLinkedLoop(out) 933 // Walk the graph to get the outline 934 for { 935 addLinkedCoord(loop, &edge.from) 936 nextVtx = edge.to 937 // Remove frees the node, so we can't use edge after this 938 removeVertexNode(graph, edge) 939 edge = findNodeForVertex(graph, &nextVtx) 940 if edge == nil { 941 break 942 } 943 } 944 } 945 } 946 947 /** 948 * Create a describing Linkedthe GeoPolygon outline(s) of a set of hexagons. 949 * Polygon outlines will follow GeoJSON MultiPolygon order: Each polygon will 950 * have one outer loop, which is first in the list, followed by any holes. 951 * 952 * It is the responsibility of the caller to call destroyLinkedPolygon on the 953 * populated linked geo structure, or the memory for that structure will 954 * not be freed. 955 * 956 * It is expected that all hexagons in the set have the same resolution and 957 * that the set contains no duplicates. Behavior is undefined if duplicates 958 * or multiple resolutions are present, and the algorithm may produce 959 * unexpected or invalid output. 960 * 961 * @param h3Set Set of hexagons 962 * @param numHexes Number of hexagons in set 963 * @param out Output polygon 964 */ 965 func h3SetToLinkedGeo(h3Set []H3Index, numHexes int, out *LinkedGeoPolygon) { 966 var graph VertexGraph 967 h3SetToVertexGraph(h3Set, numHexes, &graph) 968 _vertexGraphToLinkedGeo(&graph, out) 969 // TODO: The return value, possibly indicating an error, is discarded here - 970 // we should use this when we update the API to return a value 971 normalizeMultiPolygon(out) 972 destroyVertexGraph(&graph) 973 }