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  }