Coverage Report

Created: 2026-05-30 06:05

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