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  }