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(¢erIJK, &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(¢erIJK, &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 }