github.com/liucxer/courier@v1.7.1/h3/faceijk.go (about)

     1  package h3
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  )
     7  
     8  /**
     9   * @brief Face number and ijk coordinates on that face-centered coordinate
    10   * system
    11   */
    12  type FaceIJK struct {
    13  	face  int      ///< face number
    14  	coord CoordIJK ///< ijk coordinates on that face
    15  }
    16  
    17  /**
    18   * @brief Information to transform into an adjacent face IJK system
    19   */
    20  type FaceOrientIJK struct {
    21  	face      int      ///< face number
    22  	translate CoordIJK ///< res 0 translation relative to primary face
    23  	ccwRot60  int      ///< number of 60 degree ccw rotations relative to primary
    24  }
    25  
    26  // indexes for faceNeighbors table
    27  /** IJ quadrant faceNeighbors table direction */
    28  const IJ = 1
    29  
    30  /** KI quadrant faceNeighbors table direction */
    31  const KI = 2
    32  
    33  /** JK quadrant faceNeighbors table direction */
    34  const JK = 3
    35  
    36  /** Invalid face index */
    37  const INVALID_FACE = -1
    38  
    39  type Overage int
    40  
    41  /** Digit representing overage type */
    42  const (
    43  	/** No overage (on original face) */
    44  	NO_OVERAGE Overage = 0
    45  	/** On face edge (only occurs on substrate grids) */
    46  	FACE_EDGE Overage = 1
    47  	/** Overage on new face interior */
    48  	NEW_FACE Overage = 2
    49  )
    50  
    51  /** square root of 7 */
    52  const M_SQRT7 = 2.6457513110645905905016157536392604257102
    53  
    54  /** @brief icosahedron face centers in Lat/Lon radians */
    55  var faceCenterGeo = [NUM_ICOSA_FACES]GeoCoord{
    56  	{0.803582649718989942, 1.248397419617396099},   // face  0
    57  	{1.307747883455638156, 2.536945009877921159},   // face  1
    58  	{1.054751253523952054, -1.347517358900396623},  // face  2
    59  	{0.600191595538186799, -0.450603909469755746},  // face  3
    60  	{0.491715428198773866, 0.401988202911306943},   // face  4
    61  	{0.172745327415618701, 1.678146885280433686},   // face  5
    62  	{0.605929321571350690, 2.953923329812411617},   // face  6
    63  	{0.427370518328979641, -1.888876200336285401},  // face  7
    64  	{-0.079066118549212831, -0.733429513380867741}, // face  8
    65  	{-0.230961644455383637, 0.506495587332349035},  // face  9
    66  	{0.079066118549212831, 2.408163140208925497},   // face 10
    67  	{0.230961644455383637, -2.635097066257444203},  // face 11
    68  	{-0.172745327415618701, -1.463445768309359553}, // face 12
    69  	{-0.605929321571350690, -0.187669323777381622}, // face 13
    70  	{-0.427370518328979641, 1.252716453253507838},  // face 14
    71  	{-0.600191595538186799, 2.690988744120037492},  // face 15
    72  	{-0.491715428198773866, -2.739604450678486295}, // face 16
    73  	{-0.803582649718989942, -1.893195233972397139}, // face 17
    74  	{-1.307747883455638156, -0.604647643711872080}, // face 18
    75  	{-1.054751253523952054, 1.794075294689396615},  // face 19
    76  }
    77  
    78  /** @brief icosahedron face centers in x/y/z on the unit sphere */
    79  var faceCenterPoint = [NUM_ICOSA_FACES]Vec3d{
    80  	{0.2199307791404606, 0.6583691780274996, 0.7198475378926182},    // face  0
    81  	{-0.2139234834501421, 0.1478171829550703, 0.9656017935214205},   // face  1
    82  	{0.1092625278784797, -0.4811951572873210, 0.8697775121287253},   // face  2
    83  	{0.7428567301586791, -0.3593941678278028, 0.5648005936517033},   // face  3
    84  	{0.8112534709140969, 0.3448953237639384, 0.4721387736413930},    // face  4
    85  	{-0.1055498149613921, 0.9794457296411413, 0.1718874610009365},   // face  5
    86  	{-0.8075407579970092, 0.1533552485898818, 0.5695261994882688},   // face  6
    87  	{-0.2846148069787907, -0.8644080972654206, 0.4144792552473539},  // face  7
    88  	{0.7405621473854482, -0.6673299564565524, -0.0789837646326737},  // face  8
    89  	{0.8512303986474293, 0.4722343788582681, -0.2289137388687808},   // face  9
    90  	{-0.7405621473854481, 0.6673299564565524, 0.0789837646326737},   // face 10
    91  	{-0.8512303986474292, -0.4722343788582682, 0.2289137388687808},  // face 11
    92  	{0.1055498149613919, -0.9794457296411413, -0.1718874610009365},  // face 12
    93  	{0.8075407579970092, -0.1533552485898819, -0.5695261994882688},  // face 13
    94  	{0.2846148069787908, 0.8644080972654204, -0.4144792552473539},   // face 14
    95  	{-0.7428567301586791, 0.3593941678278027, -0.5648005936517033},  // face 15
    96  	{-0.8112534709140971, -0.3448953237639382, -0.4721387736413930}, // face 16
    97  	{-0.2199307791404607, -0.6583691780274996, -0.7198475378926182}, // face 17
    98  	{0.2139234834501420, -0.1478171829550704, -0.9656017935214205},  // face 18
    99  	{-0.1092625278784796, 0.4811951572873210, -0.8697775121287253},  // face 19
   100  }
   101  
   102  /** @brief icosahedron face ijk axes as azimuth in radians from face center to
   103   * vertex 0/1/2 respectively
   104   */
   105  var faceAxesAzRadsCII = [NUM_ICOSA_FACES][3]float64{
   106  	{5.619958268523939882, 3.525563166130744542, 1.431168063737548730}, // face  0
   107  	{5.760339081714187279, 3.665943979320991689, 1.571548876927796127}, // face  1
   108  	{0.780213654393430055, 4.969003859179821079, 2.874608756786625655}, // face  2
   109  	{0.430469363979999913, 4.619259568766391033, 2.524864466373195467}, // face  3
   110  	{6.130269123335111400, 4.035874020941915804, 1.941478918548720291}, // face  4
   111  	{2.692877706530642877, 0.598482604137447119, 4.787272808923838195}, // face  5
   112  	{2.982963003477243874, 0.888567901084048369, 5.077358105870439581}, // face  6
   113  	{3.532912002790141181, 1.438516900396945656, 5.627307105183336758}, // face  7
   114  	{3.494305004259568154, 1.399909901866372864, 5.588700106652763840}, // face  8
   115  	{3.003214169499538391, 0.908819067106342928, 5.097609271892733906}, // face  9
   116  	{5.930472956509811562, 3.836077854116615875, 1.741682751723420374}, // face 10
   117  	{0.138378484090254847, 4.327168688876645809, 2.232773586483450311}, // face 11
   118  	{0.448714947059150361, 4.637505151845541521, 2.543110049452346120}, // face 12
   119  	{0.158629650112549365, 4.347419854898940135, 2.253024752505744869}, // face 13
   120  	{5.891865957979238535, 3.797470855586042958, 1.703075753192847583}, // face 14
   121  	{2.711123289609793325, 0.616728187216597771, 4.805518392002988683}, // face 15
   122  	{3.294508837434268316, 1.200113735041072948, 5.388903939827463911}, // face 16
   123  	{3.804819692245439833, 1.710424589852244509, 5.899214794638635174}, // face 17
   124  	{3.664438879055192436, 1.570043776661997111, 5.758833981448388027}, // face 18
   125  	{2.361378999196363184, 0.266983896803167583, 4.455774101589558636}, // face 19
   126  }
   127  
   128  /** @brief Definition of which faces neighbor each other. */
   129  var faceNeighbors = [NUM_ICOSA_FACES][4]FaceOrientIJK{
   130  	{
   131  		// face 0
   132  		{0, CoordIJK{0, 0, 0}, 0}, // central face
   133  		{4, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   134  		{1, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   135  		{5, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   136  	},
   137  	{
   138  		// face 1
   139  		{1, CoordIJK{0, 0, 0}, 0}, // central face
   140  		{0, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   141  		{2, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   142  		{6, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   143  	},
   144  	{
   145  		// face 2
   146  		{2, CoordIJK{0, 0, 0}, 0}, // central face
   147  		{1, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   148  		{3, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   149  		{7, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   150  	},
   151  	{
   152  		// face 3
   153  		{3, CoordIJK{0, 0, 0}, 0}, // central face
   154  		{2, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   155  		{4, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   156  		{8, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   157  	},
   158  	{
   159  		// face 4
   160  		{4, CoordIJK{0, 0, 0}, 0}, // central face
   161  		{3, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   162  		{0, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   163  		{9, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   164  	},
   165  	{
   166  		// face 5
   167  		{5, CoordIJK{0, 0, 0}, 0},  // central face
   168  		{10, CoordIJK{2, 2, 0}, 3}, // ij quadrant
   169  		{14, CoordIJK{2, 0, 2}, 3}, // ki quadrant
   170  		{0, CoordIJK{0, 2, 2}, 3},  // jk quadrant
   171  	},
   172  	{
   173  		// face 6
   174  		{6, CoordIJK{0, 0, 0}, 0},  // central face
   175  		{11, CoordIJK{2, 2, 0}, 3}, // ij quadrant
   176  		{10, CoordIJK{2, 0, 2}, 3}, // ki quadrant
   177  		{1, CoordIJK{0, 2, 2}, 3},  // jk quadrant
   178  	},
   179  	{
   180  		// face 7
   181  		{7, CoordIJK{0, 0, 0}, 0},  // central face
   182  		{12, CoordIJK{2, 2, 0}, 3}, // ij quadrant
   183  		{11, CoordIJK{2, 0, 2}, 3}, // ki quadrant
   184  		{2, CoordIJK{0, 2, 2}, 3},  // jk quadrant
   185  	},
   186  	{
   187  		// face 8
   188  		{8, CoordIJK{0, 0, 0}, 0},  // central face
   189  		{13, CoordIJK{2, 2, 0}, 3}, // ij quadrant
   190  		{12, CoordIJK{2, 0, 2}, 3}, // ki quadrant
   191  		{3, CoordIJK{0, 2, 2}, 3},  // jk quadrant
   192  	},
   193  	{
   194  		// face 9
   195  		{9, CoordIJK{0, 0, 0}, 0},  // central face
   196  		{14, CoordIJK{2, 2, 0}, 3}, // ij quadrant
   197  		{13, CoordIJK{2, 0, 2}, 3}, // ki quadrant
   198  		{4, CoordIJK{0, 2, 2}, 3},  // jk quadrant
   199  	},
   200  	{
   201  		// face 10
   202  		{10, CoordIJK{0, 0, 0}, 0}, // central face
   203  		{5, CoordIJK{2, 2, 0}, 3},  // ij quadrant
   204  		{6, CoordIJK{2, 0, 2}, 3},  // ki quadrant
   205  		{15, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   206  	},
   207  	{
   208  		// face 11
   209  		{11, CoordIJK{0, 0, 0}, 0}, // central face
   210  		{6, CoordIJK{2, 2, 0}, 3},  // ij quadrant
   211  		{7, CoordIJK{2, 0, 2}, 3},  // ki quadrant
   212  		{16, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   213  	},
   214  	{
   215  		// face 12
   216  		{12, CoordIJK{0, 0, 0}, 0}, // central face
   217  		{7, CoordIJK{2, 2, 0}, 3},  // ij quadrant
   218  		{8, CoordIJK{2, 0, 2}, 3},  // ki quadrant
   219  		{17, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   220  	},
   221  	{
   222  		// face 13
   223  		{13, CoordIJK{0, 0, 0}, 0}, // central face
   224  		{8, CoordIJK{2, 2, 0}, 3},  // ij quadrant
   225  		{9, CoordIJK{2, 0, 2}, 3},  // ki quadrant
   226  		{18, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   227  	},
   228  	{
   229  		// face 14
   230  		{14, CoordIJK{0, 0, 0}, 0}, // central face
   231  		{9, CoordIJK{2, 2, 0}, 3},  // ij quadrant
   232  		{5, CoordIJK{2, 0, 2}, 3},  // ki quadrant
   233  		{19, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   234  	},
   235  	{
   236  		// face 15
   237  		{15, CoordIJK{0, 0, 0}, 0}, // central face
   238  		{16, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   239  		{19, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   240  		{10, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   241  	},
   242  	{
   243  		// face 16
   244  		{16, CoordIJK{0, 0, 0}, 0}, // central face
   245  		{17, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   246  		{15, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   247  		{11, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   248  	},
   249  	{
   250  		// face 17
   251  		{17, CoordIJK{0, 0, 0}, 0}, // central face
   252  		{18, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   253  		{16, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   254  		{12, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   255  	},
   256  	{
   257  		// face 18
   258  		{18, CoordIJK{0, 0, 0}, 0}, // central face
   259  		{19, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   260  		{17, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   261  		{13, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   262  	},
   263  	{
   264  		// face 19
   265  		{19, CoordIJK{0, 0, 0}, 0}, // central face
   266  		{15, CoordIJK{2, 0, 2}, 1}, // ij quadrant
   267  		{18, CoordIJK{2, 2, 0}, 5}, // ki quadrant
   268  		{14, CoordIJK{0, 2, 2}, 3}, // jk quadrant
   269  	}}
   270  
   271  /** @brief direction from the origin face to the destination face, relative to
   272   * the origin face's coordinate system, or -1 if not adjacent.
   273   */
   274  var adjacentFaceDir = [NUM_ICOSA_FACES][NUM_ICOSA_FACES]int{
   275  	{0, KI, -1, -1, IJ, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 0
   276  	{IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 1
   277  	{-1, IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 2
   278  	{-1, -1, IJ, 0, KI, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 3
   279  	{KI, -1, -1, IJ, 0, -1, -1, -1, -1, JK, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, // face 4
   280  	{JK, -1, -1, -1, -1, 0, -1, -1, -1, -1, IJ, -1, -1, -1, KI, -1, -1, -1, -1, -1}, // face 5
   281  	{-1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1, -1, -1}, // face 6
   282  	{-1, -1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1, -1}, // face 7
   283  	{-1, -1, -1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1, -1}, // face 8
   284  	{-1, -1, -1, -1, JK, -1, -1, -1, -1, 0, -1, -1, -1, KI, IJ, -1, -1, -1, -1, -1}, // face 9
   285  	{-1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1, -1, -1, -1}, // face 10
   286  	{-1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1, -1, -1}, // face 11
   287  	{-1, -1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1, -1}, // face 12
   288  	{-1, -1, -1, -1, -1, -1, -1, -1, IJ, KI, -1, -1, -1, 0, -1, -1, -1, -1, JK, -1}, // face 13
   289  	{-1, -1, -1, -1, -1, KI, -1, -1, -1, IJ, -1, -1, -1, -1, 0, -1, -1, -1, -1, JK}, // face 14
   290  	{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, -1, 0, IJ, -1, -1, KI}, // face 15
   291  	{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ, -1, -1}, // face 16
   292  	{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ, -1}, // face 17
   293  	{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, -1, -1, -1, KI, 0, IJ}, // face 18
   294  	{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, JK, IJ, -1, -1, KI, 0}, // face 19
   295  }
   296  
   297  /** @brief overage distance table */
   298  var maxDimByCIIres = []int{
   299  	2,        // res  0
   300  	-1,       // res  1
   301  	14,       // res  2
   302  	-1,       // res  3
   303  	98,       // res  4
   304  	-1,       // res  5
   305  	686,      // res  6
   306  	-1,       // res  7
   307  	4802,     // res  8
   308  	-1,       // res  9
   309  	33614,    // res 10
   310  	-1,       // res 11
   311  	235298,   // res 12
   312  	-1,       // res 13
   313  	1647086,  // res 14
   314  	-1,       // res 15
   315  	11529602, // res 16
   316  }
   317  
   318  /** @brief unit scale distance table */
   319  var unitScaleByCIIres = []int{
   320  	1,       // res  0
   321  	-1,      // res  1
   322  	7,       // res  2
   323  	-1,      // res  3
   324  	49,      // res  4
   325  	-1,      // res  5
   326  	343,     // res  6
   327  	-1,      // res  7
   328  	2401,    // res  8
   329  	-1,      // res  9
   330  	16807,   // res 10
   331  	-1,      // res 11
   332  	117649,  // res 12
   333  	-1,      // res 13
   334  	823543,  // res 14
   335  	-1,      // res 15
   336  	5764801, // res 16
   337  }
   338  
   339  /**
   340   * Encodes a coordinate on the sphere to the address *FaceIJK of the containing
   341   * cell at the specified resolution.
   342   *
   343   * @param g The spherical coordinates to encode.
   344   * @param res The desired H3 resolution for the encoding.
   345   * @param h The address *FaceIJK of the containing cell at resolution res.
   346   */
   347  func _geoToFaceIjk(g *GeoCoord, res int, h *FaceIJK) {
   348  	// first convert to hex2d
   349  	var v Vec2d
   350  	_geoToHex2d(g, res, &h.face, &v)
   351  
   352  	_hex2dToCoordIJK(&v, &h.coord)
   353  }
   354  
   355  /**
   356   * Encodes a coordinate on the sphere to the corresponding icosahedral face and
   357   * containing 2D hex coordinates relative to that face center.
   358   *
   359   * @param g The spherical coordinates to encode.
   360   * @param res The desired H3 resolution for the encoding.
   361   * @param face The icosahedral face containing the spherical coordinates.
   362   * @param v The 2D hex coordinates of the cell containing the point.
   363   */
   364  func _geoToHex2d(g *GeoCoord, res int, face *int, v *Vec2d) {
   365  	var v3d Vec3d
   366  	_geoToVec3d(g, &v3d)
   367  
   368  	// determine the icosahedron face
   369  	*face = 0
   370  	sqd := _pointSquareDist(&faceCenterPoint[0], &v3d)
   371  	for f := 1; f < NUM_ICOSA_FACES; f++ {
   372  		sqdT := _pointSquareDist(&faceCenterPoint[f], &v3d)
   373  		if sqdT < sqd {
   374  			*face = f
   375  			sqd = sqdT
   376  		}
   377  	}
   378  
   379  	// cos(r) = 1 - 2 * sin^2(r/2) = 1 - 2 * (sqd / 4) = 1 - sqd/2
   380  	r := math.Acos(1 - sqd/2)
   381  
   382  	if r < EPSILON {
   383  		v.x = 0.0
   384  		v.y = 0.0
   385  		return
   386  	}
   387  
   388  	// now have face and r, now find CCW theta from CII i-axis
   389  	theta := _posAngleRads(faceAxesAzRadsCII[*face][0] - _posAngleRads(_geoAzimuthRads(&faceCenterGeo[*face], g)))
   390  
   391  	// adjust theta for Class III (odd resolutions)
   392  	if isResClassIII(res) {
   393  		theta = _posAngleRads(theta - M_AP7_ROT_RADS)
   394  	}
   395  
   396  	// perform gnomonic scaling of r
   397  	r = math.Tan(r)
   398  
   399  	// scale for current resolution length u
   400  	r /= RES0_U_GNOMONIC
   401  	for i := 0; i < res; i++ {
   402  		r *= M_SQRT7
   403  	}
   404  
   405  	// we now have (r, theta) in hex2d with theta ccw from x-axes
   406  
   407  	// convert to local x,y
   408  	v.x = r * math.Cos(theta)
   409  	v.y = r * math.Sin(theta)
   410  }
   411  
   412  /**
   413   * Determines the center poin int spherical coordinates of a cell given by 2D
   414   * hex coordinates on a particular icosahedral face.
   415   *
   416   * @param v The 2D hex coordinates of the cell.
   417   * @param face The icosahedral face upon which the 2D hex coordinate system is
   418   *             centered.
   419   * @param res The H3 resolution of the cell.
   420   * @param substrate Indicates whether or not this grid is actually a substrate
   421   *        grid relative to the specified resolution.
   422   * @param g The spherical coordinates of the cell center point.
   423   */
   424  func _hex2dToGeo(v *Vec2d, face int, res int, substrate int, g *GeoCoord) {
   425  	// calculate (r, theta) in hex2d
   426  	r := _v2dMag(v)
   427  
   428  	if r < EPSILON {
   429  		*g = faceCenterGeo[face]
   430  		return
   431  	}
   432  
   433  	theta := math.Atan2(v.y, v.x)
   434  
   435  	// scale for current resolution length u
   436  	for i := 0; i < res; i++ {
   437  		r /= M_SQRT7
   438  	}
   439  
   440  	// scale accordingly if this is a substrate grid
   441  	if substrate != 0 {
   442  		r /= 3.0
   443  		if isResClassIII(res) {
   444  			r /= M_SQRT7
   445  		}
   446  	}
   447  
   448  	r *= RES0_U_GNOMONIC
   449  
   450  	// perform inverse gnomonic scaling of r
   451  	r = math.Atan(r)
   452  
   453  	// adjust theta for Class III
   454  	// if a substrate grid, then it's already been adjusted for Class III
   455  	if substrate == 0 && isResClassIII(res) {
   456  		theta = _posAngleRads(theta + M_AP7_ROT_RADS)
   457  	}
   458  
   459  	// find theta as an azimuth
   460  	theta = _posAngleRads(faceAxesAzRadsCII[face][0] - theta)
   461  
   462  	// now find the poat int (r,theta) from the face center
   463  	_geoAzDistanceRads(&faceCenterGeo[face], theta, r, g)
   464  }
   465  
   466  /**
   467   * Determines the center poin int spherical coordinates of a cell given by
   468   * a address *FaceIJK at a specified resolution.
   469   *
   470   * @param h The address *FaceIJK of the cell.
   471   * @param res The H3 resolution of the cell.
   472   * @param g The spherical coordinates of the cell center point.
   473   */
   474  func _faceIjkToGeo(h *FaceIJK, res int, g *GeoCoord) {
   475  	var v Vec2d
   476  	_ijkToHex2d(&h.coord, &v)
   477  	_hex2dToGeo(&v, h.face, res, 0, g)
   478  }
   479  
   480  /**
   481   * Generates the cell boundary in spherical coordinates for a pentagonal cell
   482   * given by a address *FaceIJK at a specified resolution.
   483   *
   484   * @param h The address *FaceIJK of the pentagonal cell.
   485   * @param res The H3 resolution of the cell.
   486   * @param g The spherical coordinates of the cell boundary.
   487   */
   488  func _faceIjkPentToGeoBoundary(h *FaceIJK, res int, g *GeoBoundary) {
   489  	adjRes := res
   490  	centerIJK := *h
   491  	fijkVerts := make([]FaceIJK, NUM_PENT_VERTS)
   492  
   493  	_faceIjkPentToVerts(&centerIJK, &adjRes, fijkVerts)
   494  
   495  	// convert each vertex to Lat/Lon
   496  	// adjust the face of each vertex as appropriate and introduce
   497  	// edge-crossing vertices as needed
   498  	g.numVerts = 0
   499  	var lastFijk *FaceIJK
   500  
   501  	for vert := 0; vert < NUM_PENT_VERTS+1; vert++ {
   502  
   503  		v := vert % NUM_PENT_VERTS
   504  		fijk := fijkVerts[v]
   505  
   506  		_adjustPentVertOverage(&fijk, adjRes)
   507  
   508  		// all Class III pentagon edges cross icosa edges
   509  		// note that Class II pentagons have vertices on the edge,
   510  		// not edge intersections
   511  		if isResClassIII(res) && vert > 0 {
   512  			// find hex2d of the two vertexes on the last face
   513  
   514  			tmpFijk := fijk
   515  
   516  			var orig2d0 Vec2d
   517  			_ijkToHex2d(&lastFijk.coord, &orig2d0)
   518  
   519  			currentToLastDir := adjacentFaceDir[tmpFijk.face][lastFijk.face]
   520  
   521  			fijkOrient := &faceNeighbors[tmpFijk.face][currentToLastDir]
   522  
   523  			tmpFijk.face = fijkOrient.face
   524  			ijk := &tmpFijk.coord
   525  
   526  			// rotate and translate for adjacent face
   527  			for i := 0; i < fijkOrient.ccwRot60; i++ {
   528  				_ijkRotate60ccw(ijk)
   529  			}
   530  
   531  			transVec := fijkOrient.translate
   532  			_ijkScale(&transVec, unitScaleByCIIres[adjRes]*3)
   533  			_ijkAdd(ijk, &transVec, ijk)
   534  			_ijkNormalize(ijk)
   535  
   536  			var orig2d1 Vec2d
   537  			_ijkToHex2d(ijk, &orig2d1)
   538  
   539  			// find the appropriate icosa face edge vertexes
   540  			maxDim := float64(maxDimByCIIres[adjRes])
   541  			v0 := Vec2d{3.0 * maxDim, 0.0}
   542  			v1 := Vec2d{-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}
   543  			v2 := Vec2d{-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}
   544  
   545  			var edge0 *Vec2d
   546  			var edge1 *Vec2d
   547  
   548  			f := adjacentFaceDir[tmpFijk.face][fijk.face]
   549  			switch f {
   550  			case IJ:
   551  				edge0 = &v0
   552  				edge1 = &v1
   553  				break
   554  			case JK:
   555  				edge0 = &v1
   556  				edge1 = &v2
   557  				break
   558  			default:
   559  				if f != KI {
   560  					panic(fmt.Errorf("invalid direction. expect %d, but got %d", KI, f))
   561  				}
   562  				edge0 = &v2
   563  				edge1 = &v0
   564  				break
   565  			}
   566  
   567  			// find the intersection and add the Lat/Lon poto int the result
   568  			var inter Vec2d
   569  			_v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter)
   570  
   571  			if len(g.Verts) <= g.numVerts {
   572  				g.Verts = append(g.Verts, GeoCoord{})
   573  			}
   574  			_hex2dToGeo(&inter, tmpFijk.face, adjRes, 1, &g.Verts[g.numVerts])
   575  			g.numVerts++
   576  		}
   577  
   578  		// convert vertex to Lat/Lon and add to the result
   579  		// vert == NUM_PENT_VERTS is only used to test for possible intersection
   580  		// on last edge
   581  		if vert < NUM_PENT_VERTS {
   582  			var vec Vec2d
   583  			_ijkToHex2d(&fijk.coord, &vec)
   584  
   585  			coord := GeoCoord{}
   586  			_hex2dToGeo(&vec, fijk.face, adjRes, 1, &coord)
   587  
   588  			if len(g.Verts) > g.numVerts {
   589  				g.Verts[g.numVerts] = coord
   590  			} else {
   591  				g.Verts = append(g.Verts, coord)
   592  			}
   593  			g.numVerts++
   594  		}
   595  
   596  		lastFijk = &fijk
   597  	}
   598  }
   599  
   600  /**
   601   * Get the vertices of a pentagon cell as substrate addresses *FaceIJK
   602   *
   603   * @param fijk The address *FaceIJK of the cell.
   604   * @param res The H3 resolution of the cell. This may be adjusted if
   605   *            necessary for the substrate grid resolution.
   606   * @param fijkVerts Output array for the vertices
   607   */
   608  func _faceIjkPentToVerts(fijk *FaceIJK, res *int, fijkVerts []FaceIJK) {
   609  	// get the correct set of substrate vertices for this resolution
   610  	var verts []CoordIJK
   611  	if isResClassIII(*res) {
   612  		// the vertexes of an origin-centered pentagon in a Class III resolution on
   613  		// a substrate grid with aperture sequence 33r7r. The aperture 3 gets us the
   614  		// vertices, and the 3r7r gets us to Class II. vertices listed ccw from the
   615  		// i-axes
   616  		verts = []CoordIJK{
   617  			{5, 4, 0}, // 0
   618  			{1, 5, 0}, // 1
   619  			{0, 5, 4}, // 2
   620  			{0, 1, 5}, // 3
   621  			{4, 0, 5}, // 4
   622  		}
   623  	} else {
   624  		// the vertexes of an origin-centered pentagon in a Class II resolution on a
   625  		// substrate grid with aperture sequence 33r. The aperture 3 gets us the
   626  		// vertices, and the 3r gets us back to Class II.
   627  		// vertices listed ccw from the i-axes
   628  		verts = []CoordIJK{
   629  			{2, 1, 0}, // 0
   630  			{1, 2, 0}, // 1
   631  			{0, 2, 1}, // 2
   632  			{0, 1, 2}, // 3
   633  			{1, 0, 2}, // 4
   634  		}
   635  
   636  	}
   637  
   638  	// adjust the center poto int be in an aperture 33r substrate grid
   639  	// these should be composed for speed
   640  	_downAp3(&fijk.coord)
   641  	_downAp3r(&fijk.coord)
   642  
   643  	// if res is Class III we need to add a cw aperture 7 to get to
   644  	// icosahedral Class II
   645  	if isResClassIII(*res) {
   646  		_downAp7r(&fijk.coord)
   647  		*res += 1
   648  	}
   649  
   650  	// The center pois int now in the same substrate grid as the origin
   651  	// cell vertices. Add the center posubstate int coordinates
   652  	// to each vertex to translate the vertices to that cell.
   653  	for v := 0; v < NUM_PENT_VERTS; v++ {
   654  		fijkVerts[v].face = fijk.face
   655  		_ijkAdd(&fijk.coord, &verts[v], &fijkVerts[v].coord)
   656  		_ijkNormalize(&fijkVerts[v].coord)
   657  	}
   658  }
   659  
   660  /**
   661   * Generates the cell boundary in spherical coordinates for a cell given by a
   662   * address *FaceIJK at a specified resolution.
   663   *
   664   * @param h The address *FaceIJK of the cell.
   665   * @param res The H3 resolution of the cell.
   666   * @param isPentagon Whether or not the cell is a pentagon.
   667   * @param g The spherical coordinates of the cell boundary.
   668   */
   669  func _faceIjkToGeoBoundary(h *FaceIJK, res int, isPentagon bool, g *GeoBoundary) {
   670  	if isPentagon {
   671  		_faceIjkPentToGeoBoundary(h, res, g)
   672  		return
   673  	}
   674  
   675  	centerIJK := *h
   676  
   677  	adjRes := res
   678  
   679  	fijkVerts := make([]FaceIJK, NUM_HEX_VERTS)
   680  
   681  	_faceIjkToVerts(&centerIJK, &adjRes, fijkVerts)
   682  
   683  	// convert each vertex to Lat/Lon
   684  	// adjust the face of each vertex as appropriate and introduce
   685  	// edge-crossing vertices as needed
   686  	g.numVerts = 0
   687  	lastFace := -1
   688  	lastOverage := NO_OVERAGE
   689  
   690  	for vert := 0; vert < NUM_HEX_VERTS+1; vert++ {
   691  		v := vert % NUM_HEX_VERTS
   692  
   693  		fijk := fijkVerts[v]
   694  
   695  		pentLeading4 := 0
   696  		overage := _adjustOverageClassII(&fijk, adjRes, pentLeading4, 1)
   697  
   698  		/*
   699  		   Check for edge-crossing. Each face of the underlying icosahedron is a
   700  		   different projection plane. So if an edge of the hexagon crosses an
   701  		   icosahedron edge, an additional vertex must be introduced at that
   702  		   intersection point. Then each half of the cell edge can be projected
   703  		   to geographic coordinates using the appropriate icosahedron face
   704  		   projection. Note that Class II cell edges have vertices on the face
   705  		   edge, with no edge line intersections.
   706  		*/
   707  		if isResClassIII(res) && vert > 0 && fijk.face != lastFace &&
   708  			lastOverage != FACE_EDGE {
   709  			// find hex2d of the two vertexes on original face
   710  			lastV := (v + 5) % NUM_HEX_VERTS
   711  			var orig2d0 Vec2d
   712  			_ijkToHex2d(&fijkVerts[lastV].coord, &orig2d0)
   713  
   714  			var orig2d1 Vec2d
   715  			_ijkToHex2d(&fijkVerts[v].coord, &orig2d1)
   716  
   717  			// find the appropriate icosa face edge vertexes
   718  			maxDim := float64(maxDimByCIIres[adjRes])
   719  			v0 := Vec2d{3.0 * maxDim, 0.0}
   720  			v1 := Vec2d{-1.5 * maxDim, 3.0 * M_SQRT3_2 * maxDim}
   721  			v2 := Vec2d{-1.5 * maxDim, -3.0 * M_SQRT3_2 * maxDim}
   722  
   723  			face2 := lastFace
   724  			if lastFace == centerIJK.face {
   725  				face2 = fijk.face
   726  			}
   727  
   728  			var edge0 *Vec2d
   729  			var edge1 *Vec2d
   730  
   731  			f := adjacentFaceDir[centerIJK.face][face2]
   732  			switch f {
   733  			case IJ:
   734  				edge0 = &v0
   735  				edge1 = &v1
   736  				break
   737  			case JK:
   738  				edge0 = &v1
   739  				edge1 = &v2
   740  				break
   741  			default:
   742  				if f != KI {
   743  					panic(fmt.Errorf("invalid direction. expect %d, but got %d", KI, f))
   744  				}
   745  
   746  				edge0 = &v2
   747  				edge1 = &v0
   748  				break
   749  			}
   750  
   751  			// find the intersection and add the Lat/Lon poto int the result
   752  			var inter Vec2d
   753  
   754  			_v2dIntersect(&orig2d0, &orig2d1, edge0, edge1, &inter)
   755  
   756  			/*
   757  			   If a poof int intersection occurs at a hexagon vertex, then each
   758  			   adjacent hexagon edge will lie completely on a single icosahedron
   759  			   face, and no additional vertex is required.
   760  			*/
   761  			isIntersectionAtVertex := _v2dEquals(&orig2d0, &inter) || _v2dEquals(&orig2d1, &inter)
   762  			if !isIntersectionAtVertex {
   763  				if len(g.Verts) == g.numVerts {
   764  					g.Verts = append(g.Verts, GeoCoord{})
   765  				}
   766  				_hex2dToGeo(&inter, centerIJK.face, adjRes, 1, &g.Verts[g.numVerts])
   767  				g.numVerts++
   768  			}
   769  		}
   770  
   771  		// convert vertex to Lat/Lon and add to the result
   772  		// vert == NUM_HEX_VERTS is only used to test for possible intersection
   773  		// on last edge
   774  		if vert < NUM_HEX_VERTS {
   775  			var vec Vec2d
   776  			_ijkToHex2d(&fijk.coord, &vec)
   777  			if len(g.Verts) == g.numVerts {
   778  				g.Verts = append(g.Verts, GeoCoord{})
   779  			}
   780  			_hex2dToGeo(&vec, fijk.face, adjRes, 1, &g.Verts[g.numVerts])
   781  			g.numVerts++
   782  		}
   783  
   784  		lastFace = fijk.face
   785  		lastOverage = overage
   786  	}
   787  }
   788  
   789  /**
   790   * Get the vertices of a cell as substrate addresses *FaceIJK
   791   *
   792   * @param fijk The address *FaceIJK of the cell.
   793   * @param res The H3 resolution of the cell. This may be adjusted if
   794   *            necessary for the substrate grid resolution.
   795   * @param fijkVerts Output array for the vertices
   796   */
   797  func _faceIjkToVerts(fijk *FaceIJK, res *int, fijkVerts []FaceIJK) {
   798  	// get the correct set of substrate vertices for this resolution
   799  	var verts []CoordIJK
   800  
   801  	if isResClassIII(*res) {
   802  		// the vertexes of an origin-centered cell in a Class III resolution on a
   803  		// substrate grid with aperture sequence 33r7r. The aperture 3 gets us the
   804  		// vertices, and the 3r7r gets us to Class II.
   805  		// vertices listed ccw from the i-axes
   806  		verts = []CoordIJK{
   807  			{5, 4, 0}, // 0
   808  			{1, 5, 0}, // 1
   809  			{0, 5, 4}, // 2
   810  			{0, 1, 5}, // 3
   811  			{4, 0, 5}, // 4
   812  			{5, 0, 1}, // 5
   813  		}
   814  	} else {
   815  		// the vertexes of an origin-centered cell in a Class II resolution on a
   816  		// substrate grid with aperture sequence 33r. The aperture 3 gets us the
   817  		// vertices, and the 3r gets us back to Class II.
   818  		// vertices listed ccw from the i-axes
   819  		verts = []CoordIJK{
   820  			{2, 1, 0}, // 0
   821  			{1, 2, 0}, // 1
   822  			{0, 2, 1}, // 2
   823  			{0, 1, 2}, // 3
   824  			{1, 0, 2}, // 4
   825  			{2, 0, 1}, // 5
   826  		}
   827  	}
   828  
   829  	// adjust the center poto int be in an aperture 33r substrate grid
   830  	// these should be composed for speed
   831  	_downAp3(&fijk.coord)
   832  	_downAp3r(&fijk.coord)
   833  
   834  	// if res is Class III we need to add a cw aperture 7 to get to
   835  	// icosahedral Class II
   836  	if isResClassIII(*res) {
   837  		_downAp7r(&fijk.coord)
   838  		*res += 1
   839  	}
   840  
   841  	// The center pois int now in the same substrate grid as the origin
   842  	// cell vertices. Add the center posubstate int coordinates
   843  	// to each vertex to translate the vertices to that cell.
   844  	for v := 0; v < NUM_HEX_VERTS; v++ {
   845  		fijkVerts[v].face = fijk.face
   846  		_ijkAdd(&fijk.coord, &verts[v], &fijkVerts[v].coord)
   847  		_ijkNormalize(&fijkVerts[v].coord)
   848  	}
   849  }
   850  
   851  /**
   852   * Adjusts a address *FaceIJK in place so that the resulting cell address is
   853   * relative to the correct icosahedral face.
   854   *
   855   * @param fijk The address *FaceIJK of the cell.
   856   * @param res The H3 resolution of the cell.
   857   * @param pentLeading4 Whether or not the cell is a pentagon with a leading
   858   *        digit 4.
   859   * @param substrate Whether or not the cell is in a substrate grid.
   860   * @return 0 if on original face (no overage); 1 if on face edge (only occurs
   861   *         on substrate grids); 2 if overage on new face interior
   862   */
   863  func _adjustOverageClassII(fijk *FaceIJK, res int, pentLeading4 int, substrate int) Overage {
   864  	overage := NO_OVERAGE
   865  
   866  	ijk := &fijk.coord
   867  
   868  	// get the maximum dimension value; scale if a substrate grid
   869  	maxDim := maxDimByCIIres[res]
   870  	if substrate != 0 {
   871  		maxDim *= 3
   872  	}
   873  
   874  	// check for overage
   875  	if substrate != 0 && ijk.i+ijk.j+ijk.k == maxDim {
   876  		// on edge
   877  		overage = FACE_EDGE
   878  	} else if ijk.i+ijk.j+ijk.k > maxDim {
   879  		// overage
   880  		overage = NEW_FACE
   881  
   882  		var fijkOrient *FaceOrientIJK
   883  		if ijk.k > 0 {
   884  			if ijk.j > 0 {
   885  				// jk "quadrant"
   886  				fijkOrient = &faceNeighbors[fijk.face][JK]
   887  			} else {
   888  				// ik "quadrant"
   889  				fijkOrient = &faceNeighbors[fijk.face][KI]
   890  
   891  				// adjust for the pentagonal missing sequence
   892  				if pentLeading4 != 0 {
   893  					// translate origin to center of pentagon
   894  					var origin CoordIJK
   895  					_setIJK(&origin, maxDim, 0, 0)
   896  
   897  					var tmp CoordIJK
   898  					_ijkSub(ijk, &origin, &tmp)
   899  					// rotate to adjust for the missing sequence
   900  					_ijkRotate60cw(&tmp)
   901  					// translate the origin back to the center of the triangle
   902  					_ijkAdd(&tmp, &origin, ijk)
   903  				}
   904  			}
   905  		} else {
   906  			// ij "quadrant"
   907  			fijkOrient = &faceNeighbors[fijk.face][IJ]
   908  		}
   909  
   910  		fijk.face = fijkOrient.face
   911  
   912  		// rotate and translate for adjacent face
   913  		for i := 0; i < fijkOrient.ccwRot60; i++ {
   914  			_ijkRotate60ccw(ijk)
   915  		}
   916  
   917  		transVec := fijkOrient.translate
   918  		unitScale := unitScaleByCIIres[res]
   919  		if substrate != 0 {
   920  			unitScale *= 3
   921  		}
   922  		_ijkScale(&transVec, unitScale)
   923  		_ijkAdd(ijk, &transVec, ijk)
   924  		_ijkNormalize(ijk)
   925  
   926  		// overage points on pentagon boundaries can end up on edges
   927  		if substrate != 0 && ijk.i+ijk.j+ijk.k == maxDim {
   928  			// on edge
   929  			overage = FACE_EDGE
   930  		}
   931  	}
   932  
   933  	return overage
   934  }
   935  
   936  /**
   937   * Adjusts a address *FaceIJK for a pentagon vertex in a substrate grid in
   938   * place so that the resulting cell address is relative to the correct
   939   * icosahedral face.
   940   *
   941   * @param fijk The address *FaceIJK of the cell.
   942   * @param res The H3 resolution of the cell.
   943   */
   944  func _adjustPentVertOverage(fijk *FaceIJK, res int) Overage {
   945  	pentLeading4 := 0
   946  	var overage Overage
   947  	for {
   948  		overage = _adjustOverageClassII(fijk, res, pentLeading4, 1)
   949  
   950  		if !(overage == NEW_FACE) {
   951  			break
   952  		}
   953  	}
   954  
   955  	return overage
   956  }