Coverage Report

Created: 2023-09-25 06:55

/src/h3/src/h3lib/lib/algos.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2016-2021 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 algos.c
17
 * @brief   Hexagon grid algorithms
18
 */
19
20
#include "algos.h"
21
22
#include <assert.h>
23
#include <float.h>
24
#include <math.h>
25
#include <stdbool.h>
26
#include <stdlib.h>
27
#include <string.h>
28
29
#include "alloc.h"
30
#include "baseCells.h"
31
#include "bbox.h"
32
#include "faceijk.h"
33
#include "h3Assert.h"
34
#include "h3Index.h"
35
#include "h3api.h"
36
#include "latLng.h"
37
#include "linkedGeo.h"
38
#include "polygon.h"
39
#include "vertexGraph.h"
40
41
/*
42
 * Return codes from gridDiskUnsafe and related functions.
43
 */
44
45
117M
#define MAX_ONE_RING_SIZE 7
46
1.91k
#define POLYGON_TO_CELLS_BUFFER 12
47
48
/**
49
 * Directions used for traversing a hexagonal ring counterclockwise around
50
 * {1, 0, 0}
51
 *
52
 * <pre>
53
 *      _
54
 *    _/ \\_
55
 *   / \\5/ \\
56
 *   \\0/ \\4/
57
 *   / \\_/ \\
58
 *   \\1/ \\3/
59
 *     \\2/
60
 * </pre>
61
 */
62
static const Direction DIRECTIONS[6] = {J_AXES_DIGIT, JK_AXES_DIGIT,
63
                                        K_AXES_DIGIT, IK_AXES_DIGIT,
64
                                        I_AXES_DIGIT, IJ_AXES_DIGIT};
65
66
/**
67
 * Direction used for traversing to the next outward hexagonal ring.
68
 */
69
static const Direction NEXT_RING_DIRECTION = I_AXES_DIGIT;
70
71
/**
72
 * New digit when traversing along class II grids.
73
 *
74
 * Current digit -> direction -> new digit.
75
 */
76
static const Direction NEW_DIGIT_II[7][7] = {
77
    {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT,
78
     IK_AXES_DIGIT, IJ_AXES_DIGIT},
79
    {K_AXES_DIGIT, I_AXES_DIGIT, JK_AXES_DIGIT, IJ_AXES_DIGIT, IK_AXES_DIGIT,
80
     J_AXES_DIGIT, CENTER_DIGIT},
81
    {J_AXES_DIGIT, JK_AXES_DIGIT, K_AXES_DIGIT, I_AXES_DIGIT, IJ_AXES_DIGIT,
82
     CENTER_DIGIT, IK_AXES_DIGIT},
83
    {JK_AXES_DIGIT, IJ_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT,
84
     K_AXES_DIGIT, J_AXES_DIGIT},
85
    {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, J_AXES_DIGIT,
86
     JK_AXES_DIGIT, K_AXES_DIGIT},
87
    {IK_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT,
88
     IJ_AXES_DIGIT, I_AXES_DIGIT},
89
    {IJ_AXES_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT, J_AXES_DIGIT, K_AXES_DIGIT,
90
     I_AXES_DIGIT, JK_AXES_DIGIT}};
91
92
/**
93
 * New traversal direction when traversing along class II grids.
94
 *
95
 * Current digit -> direction -> new ap7 move (at coarser level).
96
 */
97
static const Direction NEW_ADJUSTMENT_II[7][7] = {
98
    {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT,
99
     CENTER_DIGIT, CENTER_DIGIT},
100
    {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT,
101
     IK_AXES_DIGIT, CENTER_DIGIT},
102
    {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT,
103
     CENTER_DIGIT, J_AXES_DIGIT},
104
    {CENTER_DIGIT, K_AXES_DIGIT, JK_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT,
105
     CENTER_DIGIT, CENTER_DIGIT},
106
    {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT,
107
     I_AXES_DIGIT, IJ_AXES_DIGIT},
108
    {CENTER_DIGIT, IK_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT,
109
     IK_AXES_DIGIT, CENTER_DIGIT},
110
    {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT,
111
     CENTER_DIGIT, IJ_AXES_DIGIT}};
112
113
/**
114
 * New traversal direction when traversing along class III grids.
115
 *
116
 * Current digit -> direction -> new ap7 move (at coarser level).
117
 */
118
static const Direction NEW_DIGIT_III[7][7] = {
119
    {CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT,
120
     IK_AXES_DIGIT, IJ_AXES_DIGIT},
121
    {K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT,
122
     IJ_AXES_DIGIT, CENTER_DIGIT},
123
    {J_AXES_DIGIT, JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT,
124
     CENTER_DIGIT, K_AXES_DIGIT},
125
    {JK_AXES_DIGIT, I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT,
126
     K_AXES_DIGIT, J_AXES_DIGIT},
127
    {I_AXES_DIGIT, IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT,
128
     J_AXES_DIGIT, JK_AXES_DIGIT},
129
    {IK_AXES_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT,
130
     JK_AXES_DIGIT, I_AXES_DIGIT},
131
    {IJ_AXES_DIGIT, CENTER_DIGIT, K_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT,
132
     I_AXES_DIGIT, IK_AXES_DIGIT}};
133
134
/**
135
 * New traversal direction when traversing along class III grids.
136
 *
137
 * Current digit -> direction -> new ap7 move (at coarser level).
138
 */
139
static const Direction NEW_ADJUSTMENT_III[7][7] = {
140
    {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT,
141
     CENTER_DIGIT, CENTER_DIGIT},
142
    {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT,
143
     K_AXES_DIGIT, CENTER_DIGIT},
144
    {CENTER_DIGIT, CENTER_DIGIT, J_AXES_DIGIT, J_AXES_DIGIT, CENTER_DIGIT,
145
     CENTER_DIGIT, IJ_AXES_DIGIT},
146
    {CENTER_DIGIT, JK_AXES_DIGIT, J_AXES_DIGIT, JK_AXES_DIGIT, CENTER_DIGIT,
147
     CENTER_DIGIT, CENTER_DIGIT},
148
    {CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, CENTER_DIGIT, I_AXES_DIGIT,
149
     IK_AXES_DIGIT, I_AXES_DIGIT},
150
    {CENTER_DIGIT, K_AXES_DIGIT, CENTER_DIGIT, CENTER_DIGIT, IK_AXES_DIGIT,
151
     IK_AXES_DIGIT, CENTER_DIGIT},
152
    {CENTER_DIGIT, CENTER_DIGIT, IJ_AXES_DIGIT, CENTER_DIGIT, I_AXES_DIGIT,
153
     CENTER_DIGIT, IJ_AXES_DIGIT}};
154
155
/**
156
 * k value which will encompass all cells at resolution 15.
157
 * This is the largest possible k in the H3 grid system.
158
 */
159
static const int K_ALL_CELLS_AT_RES_15 = 13780510;
160
161
/**
162
 * Maximum number of cells that result from the gridDisk algorithm with the
163
 * given k. Formula source and proof: https://oeis.org/A003215
164
 *
165
 * @param   k   k value, k >= 0.
166
 * @param out   size in indexes
167
 */
168
7.93k
H3Error H3_EXPORT(maxGridDiskSize)(int k, int64_t *out) {
169
7.93k
    if (k < 0) {
170
0
        return E_DOMAIN;
171
0
    }
172
7.93k
    if (k >= K_ALL_CELLS_AT_RES_15) {
173
        // If a k value of this value or above is provided, this function will
174
        // estimate more cells than exist in the H3 grid at the finest
175
        // resolution. This is a problem since the function does signed integer
176
        // arithmetic on `k`, which could overflow. To prevent that, instead
177
        // substitute the maximum number of cells in the grid, as it should not
178
        // be possible for the gridDisk functions to exceed that. Note this is
179
        // not resolution specific. So, when resolution < 15, this function may
180
        // still estimate a size larger than the number of cells in the grid.
181
0
        return H3_EXPORT(getNumCells)(MAX_H3_RES, out);
182
0
    }
183
7.93k
    *out = 3 * (int64_t)k * ((int64_t)k + 1) + 1;
184
7.93k
    return E_SUCCESS;
185
7.93k
}
186
187
/**
188
 * Produce cells within grid distance k of the origin cell.
189
 *
190
 * k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
191
 * all neighboring cells, and so on.
192
 *
193
 * Output is placed in the provided array in no particular order. Elements of
194
 * the output array may be left zero, as can happen when crossing a pentagon.
195
 *
196
 * @param  origin   origin cell
197
 * @param  k        k >= 0
198
 * @param  out      zero-filled array which must be of size maxGridDiskSize(k)
199
 */
200
14.6M
H3Error H3_EXPORT(gridDisk)(H3Index origin, int k, H3Index *out) {
201
14.6M
    return H3_EXPORT(gridDiskDistances)(origin, k, out, NULL);
202
14.6M
}
203
204
/**
205
 * Produce cells and their distances from the given origin cell, up to
206
 * distance k.
207
 *
208
 * k-ring 0 is defined as the origin cell, k-ring 1 is defined as k-ring 0 and
209
 * all neighboring cells, and so on.
210
 *
211
 * Output is placed in the provided array in no particular order. Elements of
212
 * the output array may be left zero, as can happen when crossing a pentagon.
213
 *
214
 * @param  origin      origin cell
215
 * @param  k           k >= 0
216
 * @param  out         zero-filled array which must be of size
217
 * maxGridDiskSize(k)
218
 * @param  distances   NULL or a zero-filled array which must be of size
219
 *                     maxGridDiskSize(k)
220
 */
221
H3Error H3_EXPORT(gridDiskDistances)(H3Index origin, int k, H3Index *out,
222
14.6M
                                     int *distances) {
223
    // Optimistically try the faster gridDiskUnsafe algorithm first
224
14.6M
    const H3Error failed =
225
14.6M
        H3_EXPORT(gridDiskDistancesUnsafe)(origin, k, out, distances);
226
14.6M
    if (failed) {
227
7.93k
        int64_t maxIdx;
228
7.93k
        H3Error err = H3_EXPORT(maxGridDiskSize)(k, &maxIdx);
229
7.93k
        if (err) {
230
0
            return err;
231
0
        }
232
        // Fast algo failed, fall back to slower, correct algo
233
        // and also wipe out array because contents untrustworthy
234
7.93k
        memset(out, 0, maxIdx * sizeof(H3Index));
235
236
7.93k
        if (distances == NULL) {
237
7.93k
            distances = H3_MEMORY(calloc)(maxIdx, sizeof(int));
238
7.93k
            if (!distances) {
239
0
                return E_MEMORY_ALLOC;
240
0
            }
241
7.93k
            H3Error result = _gridDiskDistancesInternal(origin, k, out,
242
7.93k
                                                        distances, maxIdx, 0);
243
7.93k
            H3_MEMORY(free)(distances);
244
7.93k
            return result;
245
7.93k
        } else {
246
0
            memset(distances, 0, maxIdx * sizeof(int));
247
0
            return _gridDiskDistancesInternal(origin, k, out, distances, maxIdx,
248
0
                                              0);
249
0
        }
250
14.6M
    } else {
251
14.6M
        return E_SUCCESS;
252
14.6M
    }
253
14.6M
}
254
255
/**
256
 * Internal algorithm for the safe but slow version of gridDiskDistances
257
 *
258
 * Adds the origin cell to the output set (treating it as a hash set)
259
 * and recurses to its neighbors, if needed.
260
 *
261
 * @param  origin      Origin cell
262
 * @param  k           Maximum distance to move from the origin
263
 * @param  out         Array treated as a hash set, elements being either
264
 *                     H3Index or 0.
265
 * @param  distances   Scratch area, with elements paralleling the out array.
266
 *                     Elements indicate ijk distance from the origin cell to
267
 *                     the output cell
268
 * @param  maxIdx      Size of out and scratch arrays (must be
269
 * maxGridDiskSize(k))
270
 * @param  curK        Current distance from the origin
271
 */
272
H3Error _gridDiskDistancesInternal(H3Index origin, int k, H3Index *out,
273
54.9k
                                   int *distances, int64_t maxIdx, int curK) {
274
    // Put origin in the output array. out is used as a hash set.
275
54.9k
    int64_t off = origin % maxIdx;
276
102k
    while (out[off] != 0 && out[off] != origin) {
277
47.6k
        off = (off + 1) % maxIdx;
278
47.6k
    }
279
280
    // We either got a free slot in the hash set or hit a duplicate
281
    // We might need to process the duplicate anyways because we got
282
    // here on a longer path before.
283
54.9k
    if (out[off] == origin && distances[off] <= curK) return E_SUCCESS;
284
285
54.3k
    out[off] = origin;
286
54.3k
    distances[off] = curK;
287
288
    // Base case: reached an index k away from the origin.
289
54.3k
    if (curK >= k) return E_SUCCESS;
290
291
    // Recurse to all neighbors in no particular order.
292
55.5k
    for (int i = 0; i < 6; i++) {
293
47.6k
        int rotations = 0;
294
47.6k
        H3Index nextNeighbor;
295
47.6k
        H3Error neighborResult = h3NeighborRotations(origin, DIRECTIONS[i],
296
47.6k
                                                     &rotations, &nextNeighbor);
297
47.6k
        if (neighborResult != E_PENTAGON) {
298
            // E_PENTAGON is an expected case when trying to traverse off of
299
            // pentagons.
300
47.0k
            if (neighborResult != E_SUCCESS) {
301
0
                return neighborResult;
302
0
            }
303
47.0k
            neighborResult = _gridDiskDistancesInternal(
304
47.0k
                nextNeighbor, k, out, distances, maxIdx, curK + 1);
305
47.0k
            if (neighborResult) {
306
0
                return neighborResult;
307
0
            }
308
47.0k
        }
309
47.6k
    }
310
7.93k
    return E_SUCCESS;
311
7.93k
}
312
313
/**
314
 * Safe but slow version of gridDiskDistances (also called by it when needed).
315
 *
316
 * Adds the origin cell to the output set (treating it as a hash set)
317
 * and recurses to its neighbors, if needed.
318
 *
319
 * @param  origin      Origin cell
320
 * @param  k           Maximum distance to move from the origin
321
 * @param  out         Array treated as a hash set, elements being either
322
 *                     H3Index or 0.
323
 * @param  distances   Scratch area, with elements paralleling the out array.
324
 *                     Elements indicate ijk distance from the origin cell to
325
 *                     the output cell
326
 */
327
H3Error H3_EXPORT(gridDiskDistancesSafe)(H3Index origin, int k, H3Index *out,
328
0
                                         int *distances) {
329
0
    int64_t maxIdx;
330
0
    H3Error err = H3_EXPORT(maxGridDiskSize)(k, &maxIdx);
331
0
    if (err) {
332
0
        return err;
333
0
    }
334
0
    return _gridDiskDistancesInternal(origin, k, out, distances, maxIdx, 0);
335
0
}
336
337
/**
338
 * Returns the hexagon index neighboring the origin, in the direction dir.
339
 *
340
 * Implementation note: The only reachable case where this returns 0 is if the
341
 * origin is a pentagon and the translation is in the k direction. Thus,
342
 * 0 can only be returned if origin is a pentagon.
343
 *
344
 * @param origin Origin index
345
 * @param dir Direction to move in
346
 * @param rotations Number of ccw rotations to perform to reorient the
347
 *                  translation vector. Will be modified to the new number of
348
 *                  rotations to perform (such as when crossing a face edge.)
349
 * @param out H3Index of the specified neighbor if succesful
350
 * @return E_SUCCESS on success
351
 */
352
H3Error h3NeighborRotations(H3Index origin, Direction dir, int *rotations,
353
102M
                            H3Index *out) {
354
102M
    H3Index current = origin;
355
356
102M
    if (dir < CENTER_DIGIT || dir >= INVALID_DIGIT) {
357
0
        return E_FAILED;
358
0
    }
359
    // Ensure that rotations is modulo'd by 6 before any possible addition,
360
    // to protect against signed integer overflow.
361
102M
    *rotations = *rotations % 6;
362
105M
    for (int i = 0; i < *rotations; i++) {
363
2.41M
        dir = _rotate60ccw(dir);
364
2.41M
    }
365
366
102M
    int newRotations = 0;
367
102M
    int oldBaseCell = H3_GET_BASE_CELL(current);
368
102M
    if (NEVER(oldBaseCell < 0) || oldBaseCell >= NUM_BASE_CELLS) {
369
        // Base cells less than zero can not be represented in an index
370
0
        return E_CELL_INVALID;
371
0
    }
372
102M
    Direction oldLeadingDigit = _h3LeadingNonZeroDigit(current);
373
374
    // Adjust the indexing digits and, if needed, the base cell.
375
102M
    int r = H3_GET_RESOLUTION(current) - 1;
376
178M
    while (true) {
377
178M
        if (r == -1) {
378
1.74M
            H3_SET_BASE_CELL(current, baseCellNeighbors[oldBaseCell][dir]);
379
1.74M
            newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir];
380
381
1.74M
            if (H3_GET_BASE_CELL(current) == INVALID_BASE_CELL) {
382
                // Adjust for the deleted k vertex at the base cell level.
383
                // This edge actually borders a different neighbor.
384
8.99k
                H3_SET_BASE_CELL(current,
385
8.99k
                                 baseCellNeighbors[oldBaseCell][IK_AXES_DIGIT]);
386
8.99k
                newRotations =
387
8.99k
                    baseCellNeighbor60CCWRots[oldBaseCell][IK_AXES_DIGIT];
388
389
                // perform the adjustment for the k-subsequence we're skipping
390
                // over.
391
8.99k
                current = _h3Rotate60ccw(current);
392
8.99k
                *rotations = *rotations + 1;
393
8.99k
            }
394
395
1.74M
            break;
396
176M
        } else {
397
176M
            Direction oldDigit = H3_GET_INDEX_DIGIT(current, r + 1);
398
176M
            Direction nextDir;
399
176M
            if (oldDigit == INVALID_DIGIT) {
400
                // Only possible on invalid input
401
0
                return E_CELL_INVALID;
402
176M
            } else if (isResolutionClassIII(r + 1)) {
403
100M
                H3_SET_INDEX_DIGIT(current, r + 1, NEW_DIGIT_II[oldDigit][dir]);
404
100M
                nextDir = NEW_ADJUSTMENT_II[oldDigit][dir];
405
100M
            } else {
406
76.6M
                H3_SET_INDEX_DIGIT(current, r + 1,
407
76.6M
                                   NEW_DIGIT_III[oldDigit][dir]);
408
76.6M
                nextDir = NEW_ADJUSTMENT_III[oldDigit][dir];
409
76.6M
            }
410
411
176M
            if (nextDir != CENTER_DIGIT) {
412
75.8M
                dir = nextDir;
413
75.8M
                r--;
414
100M
            } else {
415
                // No more adjustment to perform
416
100M
                break;
417
100M
            }
418
176M
        }
419
178M
    }
420
421
102M
    int newBaseCell = H3_GET_BASE_CELL(current);
422
102M
    if (_isBaseCellPentagon(newBaseCell)) {
423
10.7M
        int alreadyAdjustedKSubsequence = 0;
424
425
        // force rotation out of missing k-axes sub-sequence
426
10.7M
        if (_h3LeadingNonZeroDigit(current) == K_AXES_DIGIT) {
427
44.2k
            if (oldBaseCell != newBaseCell) {
428
                // in this case, we traversed into the deleted
429
                // k subsequence of a pentagon base cell.
430
                // We need to rotate out of that case depending
431
                // on how we got here.
432
                // check for a cw/ccw offset face; default is ccw
433
434
10.9k
                if (ALWAYS(_baseCellIsCwOffset(
435
10.9k
                        newBaseCell,
436
10.9k
                        baseCellData[oldBaseCell].homeFijk.face))) {
437
10.9k
                    current = _h3Rotate60cw(current);
438
10.9k
                } else {
439
                    // See cwOffsetPent in testGridDisk.c for why this is
440
                    // unreachable.
441
0
                    current = _h3Rotate60ccw(current);
442
0
                }
443
0
                alreadyAdjustedKSubsequence = 1;
444
33.2k
            } else {
445
                // In this case, we traversed into the deleted
446
                // k subsequence from within the same pentagon
447
                // base cell.
448
33.2k
                if (oldLeadingDigit == CENTER_DIGIT) {
449
                    // Undefined: the k direction is deleted from here
450
586
                    return E_PENTAGON;
451
32.6k
                } else if (oldLeadingDigit == JK_AXES_DIGIT) {
452
                    // Rotate out of the deleted k subsequence
453
                    // We also need an additional change to the direction we're
454
                    // moving in
455
17.2k
                    current = _h3Rotate60ccw(current);
456
17.2k
                    *rotations = *rotations + 1;
457
17.2k
                } else if (oldLeadingDigit == IK_AXES_DIGIT) {
458
                    // Rotate out of the deleted k subsequence
459
                    // We also need an additional change to the direction we're
460
                    // moving in
461
15.4k
                    current = _h3Rotate60cw(current);
462
15.4k
                    *rotations = *rotations + 5;
463
15.4k
                } else {
464
                    // TODO: Should never occur, but is reachable by fuzzer
465
0
                    return E_FAILED;
466
0
                }
467
33.2k
            }
468
44.2k
        }
469
470
10.9M
        for (int i = 0; i < newRotations; i++)
471
203k
            current = _h3RotatePent60ccw(current);
472
473
        // Account for differing orientation of the base cells (this edge
474
        // might not follow properties of some other edges.)
475
10.7M
        if (oldBaseCell != newBaseCell) {
476
141k
            if (_isBaseCellPolarPentagon(newBaseCell)) {
477
                // 'polar' base cells behave differently because they have all
478
                // i neighbors.
479
46.6k
                if (oldBaseCell != 118 && oldBaseCell != 8 &&
480
46.6k
                    _h3LeadingNonZeroDigit(current) != JK_AXES_DIGIT) {
481
29.4k
                    *rotations = *rotations + 1;
482
29.4k
                }
483
94.7k
            } else if (_h3LeadingNonZeroDigit(current) == IK_AXES_DIGIT &&
484
94.7k
                       !alreadyAdjustedKSubsequence) {
485
                // account for distortion introduced to the 5 neighbor by the
486
                // deleted k subsequence.
487
12.6k
                *rotations = *rotations + 1;
488
12.6k
            }
489
141k
        }
490
91.9M
    } else {
491
94.5M
        for (int i = 0; i < newRotations; i++)
492
2.57M
            current = _h3Rotate60ccw(current);
493
91.9M
    }
494
495
102M
    *rotations = (*rotations + newRotations) % 6;
496
102M
    *out = current;
497
498
102M
    return E_SUCCESS;
499
102M
}
500
501
/**
502
 * Get the direction from the origin to a given neighbor. This is effectively
503
 * the reverse operation for h3NeighborRotations. Returns INVALID_DIGIT if the
504
 * cells are not neighbors.
505
 *
506
 * TODO: This is currently a brute-force algorithm, but as it's O(6) that's
507
 * probably acceptable.
508
 */
509
0
Direction directionForNeighbor(H3Index origin, H3Index destination) {
510
0
    bool isPent = H3_EXPORT(isPentagon)(origin);
511
    // Checks each neighbor, in order, to determine which direction the
512
    // destination neighbor is located. Skips CENTER_DIGIT since that
513
    // would be the origin; skips deleted K direction for pentagons.
514
0
    for (Direction direction = isPent ? J_AXES_DIGIT : K_AXES_DIGIT;
515
0
         direction < NUM_DIGITS; direction++) {
516
0
        H3Index neighbor;
517
0
        int rotations = 0;
518
0
        H3Error neighborError =
519
0
            h3NeighborRotations(origin, direction, &rotations, &neighbor);
520
0
        if (!neighborError && neighbor == destination) {
521
0
            return direction;
522
0
        }
523
0
    }
524
0
    return INVALID_DIGIT;
525
0
}
526
527
/**
528
 * gridDiskUnsafe produces indexes within k distance of the origin index.
529
 * Output behavior is undefined when one of the indexes returned by this
530
 * function is a pentagon or is in the pentagon distortion area.
531
 *
532
 * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and
533
 * all neighboring indexes, and so on.
534
 *
535
 * Output is placed in the provided array in order of increasing distance from
536
 * the origin.
537
 *
538
 * @param origin Origin location.
539
 * @param k k >= 0
540
 * @param out Array which must be of size maxGridDiskSize(k).
541
 * @return 0 if no pentagon or pentagonal distortion area was encountered.
542
 */
543
0
H3Error H3_EXPORT(gridDiskUnsafe)(H3Index origin, int k, H3Index *out) {
544
0
    return H3_EXPORT(gridDiskDistancesUnsafe)(origin, k, out, NULL);
545
0
}
546
547
/**
548
 * gridDiskDistancesUnsafe produces indexes within k distance of the origin
549
 * index. Output behavior is undefined when one of the indexes returned by this
550
 * function is a pentagon or is in the pentagon distortion area.
551
 *
552
 * k-ring 0 is defined as the origin index, k-ring 1 is defined as k-ring 0 and
553
 * all neighboring indexes, and so on.
554
 *
555
 * Output is placed in the provided array in order of increasing distance from
556
 * the origin. The distances in hexagons is placed in the distances array at
557
 * the same offset.
558
 *
559
 * @param origin Origin location.
560
 * @param k k >= 0
561
 * @param out Array which must be of size maxGridDiskSize(k).
562
 * @param distances Null or array which must be of size maxGridDiskSize(k).
563
 * @return 0 if no pentagon or pentagonal distortion area was encountered.
564
 */
565
H3Error H3_EXPORT(gridDiskDistancesUnsafe)(H3Index origin, int k, H3Index *out,
566
14.6M
                                           int *distances) {
567
    // Return codes:
568
    // 1 Pentagon was encountered
569
    // 2 Pentagon distortion (deleted k subsequence) was encountered
570
    // Pentagon being encountered is not itself a problem; really the deleted
571
    // k-subsequence is the problem, but for compatibility reasons we fail on
572
    // the pentagon.
573
14.6M
    if (k < 0) {
574
0
        return E_DOMAIN;
575
0
    }
576
577
    // k must be >= 0, so origin is always needed
578
14.6M
    int idx = 0;
579
14.6M
    out[idx] = origin;
580
14.6M
    if (distances) {
581
0
        distances[idx] = 0;
582
0
    }
583
14.6M
    idx++;
584
585
14.6M
    if (H3_EXPORT(isPentagon)(origin)) {
586
        // Pentagon was encountered; bail out as user doesn't want this.
587
1.21k
        return E_PENTAGON;
588
1.21k
    }
589
590
    // 0 < ring <= k, current ring
591
14.6M
    int ring = 1;
592
    // 0 <= direction < 6, current side of the ring
593
14.6M
    int direction = 0;
594
    // 0 <= i < ring, current position on the side of the ring
595
14.6M
    int i = 0;
596
    // Number of 60 degree ccw rotations to perform on the direction (based on
597
    // which faces have been crossed.)
598
14.6M
    int rotations = 0;
599
600
102M
    while (ring <= k) {
601
88.0M
        if (direction == 0 && i == 0) {
602
            // Not putting in the output set as it will be done later, at
603
            // the end of this ring.
604
14.6M
            H3Error neighborResult = h3NeighborRotations(
605
14.6M
                origin, NEXT_RING_DIRECTION, &rotations, &origin);
606
14.6M
            if (neighborResult) {
607
                // Should not be possible because `origin` would have to be a
608
                // pentagon
609
                // TODO: Reachable via fuzzer
610
0
                return neighborResult;
611
0
            }
612
613
14.6M
            if (H3_EXPORT(isPentagon)(origin)) {
614
                // Pentagon was encountered; bail out as user doesn't want this.
615
2.04k
                return E_PENTAGON;
616
2.04k
            }
617
14.6M
        }
618
619
88.0M
        H3Error neighborResult = h3NeighborRotations(
620
88.0M
            origin, DIRECTIONS[direction], &rotations, &origin);
621
88.0M
        if (neighborResult) {
622
0
            return neighborResult;
623
0
        }
624
88.0M
        out[idx] = origin;
625
88.0M
        if (distances) {
626
0
            distances[idx] = ring;
627
0
        }
628
88.0M
        idx++;
629
630
88.0M
        i++;
631
        // Check if end of this side of the k-ring
632
88.0M
        if (i == ring) {
633
88.0M
            i = 0;
634
88.0M
            direction++;
635
            // Check if end of this ring.
636
88.0M
            if (direction == 6) {
637
14.6M
                direction = 0;
638
14.6M
                ring++;
639
14.6M
            }
640
88.0M
        }
641
642
88.0M
        if (H3_EXPORT(isPentagon)(origin)) {
643
            // Pentagon was encountered; bail out as user doesn't want this.
644
4.67k
            return E_PENTAGON;
645
4.67k
        }
646
88.0M
    }
647
14.6M
    return E_SUCCESS;
648
14.6M
}
649
650
/**
651
 * gridDisksUnsafe takes an array of input hex IDs and a max k-ring and returns
652
 * an array of hexagon IDs sorted first by the original hex IDs and then by the
653
 * k-ring (0 to max), with no guaranteed sorting within each k-ring group.
654
 *
655
 * @param h3Set A pointer to an array of H3Indexes
656
 * @param length The total number of H3Indexes in h3Set
657
 * @param k The number of rings to generate
658
 * @param out A pointer to the output memory to dump the new set of H3Indexes to
659
 *            The memory block should be equal to maxGridDiskSize(k) * length
660
 * @return 0 if no pentagon is encountered. Cannot trust output otherwise
661
 */
662
H3Error H3_EXPORT(gridDisksUnsafe)(H3Index *h3Set, int length, int k,
663
0
                                   H3Index *out) {
664
0
    H3Index *segment;
665
0
    int64_t segmentSize;
666
0
    H3Error err = H3_EXPORT(maxGridDiskSize)(k, &segmentSize);
667
0
    if (err) {
668
0
        return err;
669
0
    }
670
0
    for (int i = 0; i < length; i++) {
671
        // Determine the appropriate segment of the output array to operate on
672
0
        segment = out + i * segmentSize;
673
0
        H3Error failed = H3_EXPORT(gridDiskUnsafe)(h3Set[i], k, segment);
674
0
        if (failed) return failed;
675
0
    }
676
0
    return E_SUCCESS;
677
0
}
678
679
/**
680
 * Returns the "hollow" ring of hexagons at exactly grid distance k from
681
 * the origin hexagon. In particular, k=0 returns just the origin hexagon.
682
 *
683
 * A nonzero failure code may be returned in some cases, for example,
684
 * if a pentagon is encountered.
685
 * Failure cases may be fixed in future versions.
686
 *
687
 * @param origin Origin location.
688
 * @param k k >= 0
689
 * @param out Array which must be of size 6 * k (or 1 if k == 0)
690
 * @return 0 if successful; nonzero otherwise.
691
 */
692
0
H3Error H3_EXPORT(gridRingUnsafe)(H3Index origin, int k, H3Index *out) {
693
    // Short-circuit on 'identity' ring
694
0
    if (k == 0) {
695
0
        out[0] = origin;
696
0
        return E_SUCCESS;
697
0
    }
698
0
    int idx = 0;
699
    // Number of 60 degree ccw rotations to perform on the direction (based on
700
    // which faces have been crossed.)
701
0
    int rotations = 0;
702
    // Scratch structure for checking for pentagons
703
0
    if (H3_EXPORT(isPentagon)(origin)) {
704
        // Pentagon was encountered; bail out as user doesn't want this.
705
0
        return E_PENTAGON;
706
0
    }
707
708
0
    for (int ring = 0; ring < k; ring++) {
709
0
        H3Error neighborResult = h3NeighborRotations(
710
0
            origin, NEXT_RING_DIRECTION, &rotations, &origin);
711
0
        if (neighborResult) {
712
            // Should not be possible because `origin` would have to be a
713
            // pentagon
714
            // TODO: Reachable via fuzzer
715
0
            return neighborResult;
716
0
        }
717
718
0
        if (H3_EXPORT(isPentagon)(origin)) {
719
0
            return E_PENTAGON;
720
0
        }
721
0
    }
722
723
0
    H3Index lastIndex = origin;
724
725
0
    out[idx] = origin;
726
0
    idx++;
727
728
0
    for (int direction = 0; direction < 6; direction++) {
729
0
        for (int pos = 0; pos < k; pos++) {
730
0
            H3Error neighborResult = h3NeighborRotations(
731
0
                origin, DIRECTIONS[direction], &rotations, &origin);
732
0
            if (neighborResult) {
733
                // Should not be possible because `origin` would have to be a
734
                // pentagon
735
                // TODO: Reachable via fuzzer
736
0
                return neighborResult;
737
0
            }
738
739
            // Skip the very last index, it was already added. We do
740
            // however need to traverse to it because of the pentagonal
741
            // distortion check, below.
742
0
            if (pos != k - 1 || direction != 5) {
743
0
                out[idx] = origin;
744
0
                idx++;
745
746
0
                if (H3_EXPORT(isPentagon)(origin)) {
747
0
                    return E_PENTAGON;
748
0
                }
749
0
            }
750
0
        }
751
0
    }
752
753
    // Check that this matches the expected lastIndex, if it doesn't,
754
    // it indicates pentagonal distortion occurred and we should report
755
    // failure.
756
0
    if (lastIndex != origin) {
757
0
        return E_PENTAGON;
758
0
    } else {
759
0
        return E_SUCCESS;
760
0
    }
761
0
}
762
763
/**
764
 * maxPolygonToCellsSize returns the number of cells to allocate space for
765
 * when performing a polygonToCells on the given GeoJSON-like data structure.
766
 *
767
 * The size is the maximum of either the number of points in the geoloop or the
768
 * number of cells in the bounding box of the geoloop.
769
 *
770
 * @param geoPolygon A GeoJSON-like data structure indicating the poly to fill
771
 * @param res Hexagon resolution (0-15)
772
 * @param out number of cells to allocate for
773
 * @return 0 (E_SUCCESS) on success.
774
 */
775
H3Error H3_EXPORT(maxPolygonToCellsSize)(const GeoPolygon *geoPolygon, int res,
776
1.94k
                                         uint32_t flags, int64_t *out) {
777
1.94k
    if (flags != 0) {
778
0
        return E_OPTION_INVALID;
779
0
    }
780
    // Get the bounding box for the GeoJSON-like struct
781
1.94k
    BBox bbox;
782
1.94k
    const GeoLoop geoloop = geoPolygon->geoloop;
783
1.94k
    bboxFromGeoLoop(&geoloop, &bbox);
784
1.94k
    int64_t numHexagons;
785
1.94k
    H3Error estimateErr = bboxHexEstimate(&bbox, res, &numHexagons);
786
1.94k
    if (estimateErr) {
787
32
        return estimateErr;
788
32
    }
789
    // This algorithm assumes that the number of vertices is usually less than
790
    // the number of hexagons, but when it's wrong, this will keep it from
791
    // failing
792
1.91k
    int totalVerts = geoloop.numVerts;
793
16.0k
    for (int i = 0; i < geoPolygon->numHoles; i++) {
794
14.1k
        totalVerts += geoPolygon->holes[i].numVerts;
795
14.1k
    }
796
1.91k
    if (numHexagons < totalVerts) numHexagons = totalVerts;
797
    // When the polygon is very small, near an icosahedron edge and is an odd
798
    // resolution, the line tracing needs an extra buffer than the estimator
799
    // function provides (but beefing that up to cover causes most situations to
800
    // overallocate memory)
801
1.91k
    numHexagons += POLYGON_TO_CELLS_BUFFER;
802
1.91k
    *out = numHexagons;
803
1.91k
    return E_SUCCESS;
804
1.94k
}
805
806
/**
807
 * _getEdgeHexagons takes a given geoloop ring (either the main geoloop or
808
 * one of the holes) and traces it with hexagons and updates the search and
809
 * found memory blocks. This is used for determining the initial hexagon set
810
 * for the polygonToCells algorithm to execute on.
811
 *
812
 * @param geoloop The geoloop (or hole) to be traced
813
 * @param numHexagons The maximum number of hexagons possible for the geoloop
814
 *                    (also the bounds of the search and found arrays)
815
 * @param res The hexagon resolution (0-15)
816
 * @param numSearchHexes The number of hexagons found so far to be searched
817
 * @param search The block of memory containing the hexagons to search from
818
 * @param found The block of memory containing the hexagons found from the
819
 * search
820
 *
821
 * @return An error code if the hash function cannot insert a found hexagon
822
 *         into the found array.
823
 */
824
H3Error _getEdgeHexagons(const GeoLoop *geoloop, int64_t numHexagons, int res,
825
                         int64_t *numSearchHexes, H3Index *search,
826
4.60k
                         H3Index *found) {
827
203k
    for (int i = 0; i < geoloop->numVerts; i++) {
828
198k
        LatLng origin = geoloop->verts[i];
829
198k
        LatLng destination = i == geoloop->numVerts - 1 ? geoloop->verts[0]
830
198k
                                                        : geoloop->verts[i + 1];
831
198k
        int64_t numHexesEstimate;
832
198k
        H3Error estimateErr =
833
198k
            lineHexEstimate(&origin, &destination, res, &numHexesEstimate);
834
198k
        if (estimateErr) {
835
148
            return estimateErr;
836
148
        }
837
14.1M
        for (int64_t j = 0; j < numHexesEstimate; j++) {
838
13.9M
            LatLng interpolate;
839
13.9M
            interpolate.lat =
840
13.9M
                (origin.lat * (numHexesEstimate - j) / numHexesEstimate) +
841
13.9M
                (destination.lat * j / numHexesEstimate);
842
13.9M
            interpolate.lng =
843
13.9M
                (origin.lng * (numHexesEstimate - j) / numHexesEstimate) +
844
13.9M
                (destination.lng * j / numHexesEstimate);
845
13.9M
            H3Index pointHex;
846
13.9M
            H3Error e = H3_EXPORT(latLngToCell)(&interpolate, res, &pointHex);
847
13.9M
            if (e) {
848
47
                return e;
849
47
            }
850
            // A simple hash to store the hexagon, or move to another place if
851
            // needed
852
13.9M
            int64_t loc = (int64_t)(pointHex % numHexagons);
853
13.9M
            int64_t loopCount = 0;
854
913M
            while (found[loc] != 0) {
855
                // If this conditional is reached, the `found` memory block is
856
                // too small for the given polygon. This should not happen.
857
                // TODO: Reachable via fuzzer
858
904M
                if (loopCount > numHexagons) return E_FAILED;
859
904M
                if (found[loc] == pointHex)
860
4.91M
                    break;  // At least two points of the geoloop index to the
861
                            // same cell
862
899M
                loc = (loc + 1) % numHexagons;
863
899M
                loopCount++;
864
899M
            }
865
13.9M
            if (found[loc] == pointHex)
866
4.91M
                continue;  // Skip this hex, already exists in the found hash
867
            // Otherwise, set it in the found hash for now
868
9.04M
            found[loc] = pointHex;
869
870
9.04M
            search[*numSearchHexes] = pointHex;
871
9.04M
            (*numSearchHexes)++;
872
9.04M
        }
873
198k
    }
874
4.32k
    return E_SUCCESS;
875
4.60k
}
876
877
/**
878
 * polygonToCells takes a given GeoJSON-like data structure and preallocated,
879
 * zeroed memory, and fills it with the hexagons that are contained by
880
 * the GeoJSON-like data structure.
881
 *
882
 * This implementation traces the GeoJSON geoloop(s) in cartesian space with
883
 * hexagons, tests them and their neighbors to be contained by the geoloop(s),
884
 * and then any newly found hexagons are used to test again until no new
885
 * hexagons are found.
886
 *
887
 * @param geoPolygon The geoloop and holes defining the relevant area
888
 * @param res The Hexagon resolution (0-15)
889
 * @param out The slab of zeroed memory to write to. Assumed to be big enough.
890
 */
891
H3Error H3_EXPORT(polygonToCells)(const GeoPolygon *geoPolygon, int res,
892
908
                                  uint32_t flags, H3Index *out) {
893
908
    if (flags != 0) {
894
0
        return E_OPTION_INVALID;
895
0
    }
896
    // One of the goals of the polygonToCells algorithm is that two adjacent
897
    // polygons with zero overlap have zero overlapping hexagons. That the
898
    // hexagons are uniquely assigned. There are a few approaches to take here,
899
    // such as deciding based on which polygon has the greatest overlapping area
900
    // of the hexagon, or the most number of contained points on the hexagon
901
    // (using the center point as a tiebreaker).
902
    //
903
    // But if the polygons are convex, both of these more complex algorithms can
904
    // be reduced down to checking whether or not the center of the hexagon is
905
    // contained in the polygon, and so this is the approach that this
906
    // polygonToCells algorithm will follow, as it's simpler, faster, and the
907
    // error for concave polygons is still minimal (only affecting concave
908
    // shapes on the order of magnitude of the hexagon size or smaller, not
909
    // impacting larger concave shapes)
910
    //
911
    // This first part is identical to the maxPolygonToCellsSize above.
912
913
    // Get the bounding boxes for the polygon and any holes
914
908
    BBox *bboxes = H3_MEMORY(malloc)((geoPolygon->numHoles + 1) * sizeof(BBox));
915
908
    if (!bboxes) {
916
0
        return E_MEMORY_ALLOC;
917
0
    }
918
908
    bboxesFromGeoPolygon(geoPolygon, bboxes);
919
920
    // Get the estimated number of hexagons and allocate some temporary memory
921
    // for the hexagons
922
908
    int64_t numHexagons;
923
908
    H3Error numHexagonsError =
924
908
        H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &numHexagons);
925
908
    if (numHexagonsError) {
926
0
        H3_MEMORY(free)(bboxes);
927
0
        return numHexagonsError;
928
0
    }
929
908
    H3Index *search = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index));
930
908
    if (!search) {
931
0
        H3_MEMORY(free)(bboxes);
932
0
        return E_MEMORY_ALLOC;
933
0
    }
934
908
    H3Index *found = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index));
935
908
    if (!found) {
936
0
        H3_MEMORY(free)(bboxes);
937
0
        H3_MEMORY(free)(search);
938
0
        return E_MEMORY_ALLOC;
939
0
    }
940
941
    // Some metadata for tracking the state of the search and found memory
942
    // blocks
943
908
    int64_t numSearchHexes = 0;
944
908
    int64_t numFoundHexes = 0;
945
946
    // 1. Trace the hexagons along the polygon defining the outer geoloop and
947
    // add them to the search hash. The hexagon containing the geoloop point
948
    // may or may not be contained by the geoloop (as the hexagon's center
949
    // point may be outside of the boundary.)
950
908
    const GeoLoop geoloop = geoPolygon->geoloop;
951
908
    H3Error edgeHexError = _getEdgeHexagons(&geoloop, numHexagons, res,
952
908
                                            &numSearchHexes, search, found);
953
    // If this branch is reached, we have exceeded the maximum number of
954
    // hexagons possible and need to clean up the allocated memory.
955
    // TODO: Reachable via fuzzer
956
908
    if (edgeHexError) {
957
153
        H3_MEMORY(free)(search);
958
153
        H3_MEMORY(free)(found);
959
153
        H3_MEMORY(free)(bboxes);
960
153
        return edgeHexError;
961
153
    }
962
963
    // 2. Iterate over all holes, trace the polygons defining the holes with
964
    // hexagons and add to only the search hash. We're going to temporarily use
965
    // the `found` hash to use for dedupe purposes and then re-zero it once
966
    // we're done here, otherwise we'd have to scan the whole set on each insert
967
    // to make sure there's no duplicates, which is very inefficient.
968
4.32k
    for (int i = 0; i < geoPolygon->numHoles; i++) {
969
3.69k
        GeoLoop *hole = &(geoPolygon->holes[i]);
970
3.69k
        edgeHexError = _getEdgeHexagons(hole, numHexagons, res, &numSearchHexes,
971
3.69k
                                        search, found);
972
        // If this branch is reached, we have exceeded the maximum number of
973
        // hexagons possible and need to clean up the allocated memory.
974
        // TODO: Reachable via fuzzer
975
3.69k
        if (edgeHexError) {
976
131
            H3_MEMORY(free)(search);
977
131
            H3_MEMORY(free)(found);
978
131
            H3_MEMORY(free)(bboxes);
979
131
            return edgeHexError;
980
131
        }
981
3.69k
    }
982
983
    // 3. Re-zero the found hash so it can be used in the main loop below
984
193M
    for (int64_t i = 0; i < numHexagons; i++) found[i] = H3_NULL;
985
986
    // 4. Begin main loop. While the search hash is not empty do the following
987
46.7k
    while (numSearchHexes > 0) {
988
        // Iterate through all hexagons in the current search hash, then loop
989
        // through all neighbors and test Point-in-Poly, if point-in-poly
990
        // succeeds, add to out and found hashes if not already there.
991
46.3k
        int64_t currentSearchNum = 0;
992
46.3k
        int64_t i = 0;
993
14.7M
        while (currentSearchNum < numSearchHexes) {
994
14.6M
            H3Index ring[MAX_ONE_RING_SIZE] = {0};
995
14.6M
            H3Index searchHex = search[i];
996
14.6M
            H3_EXPORT(gridDisk)(searchHex, 1, ring);
997
117M
            for (int j = 0; j < MAX_ONE_RING_SIZE; j++) {
998
102M
                if (ring[j] == H3_NULL) {
999
1.20k
                    continue;  // Skip if this was a pentagon and only had 5
1000
                               // neighbors
1001
1.20k
                }
1002
1003
102M
                H3Index hex = ring[j];
1004
1005
                // A simple hash to store the hexagon, or move to another place
1006
                // if needed. This MUST be done before the point-in-poly check
1007
                // since that's far more expensive
1008
102M
                int64_t loc = (int64_t)(hex % numHexagons);
1009
102M
                int64_t loopCount = 0;
1010
952M
                while (out[loc] != 0) {
1011
                    // If this branch is reached, we have exceeded the maximum
1012
                    // number of hexagons possible and need to clean up the
1013
                    // allocated memory.
1014
                    // TODO: Reachable via fuzzer
1015
910M
                    if (loopCount > numHexagons) {
1016
195
                        H3_MEMORY(free)(search);
1017
195
                        H3_MEMORY(free)(found);
1018
195
                        H3_MEMORY(free)(bboxes);
1019
195
                        return E_FAILED;
1020
195
                    }
1021
910M
                    if (out[loc] == hex) break;  // Skip duplicates found
1022
850M
                    loc = (loc + 1) % numHexagons;
1023
850M
                    loopCount++;
1024
850M
                }
1025
102M
                if (out[loc] == hex) {
1026
60.1M
                    continue;  // Skip this hex, already exists in the out hash
1027
60.1M
                }
1028
1029
                // Check if the hexagon is in the polygon or not
1030
42.5M
                LatLng hexCenter;
1031
42.5M
                H3_EXPORT(cellToLatLng)(hex, &hexCenter);
1032
1033
                // If not, skip
1034
42.5M
                if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) {
1035
32.2M
                    continue;
1036
32.2M
                }
1037
1038
                // Otherwise set it in the output array
1039
10.3M
                out[loc] = hex;
1040
1041
                // Set the hexagon in the found hash
1042
10.3M
                found[numFoundHexes] = hex;
1043
10.3M
                numFoundHexes++;
1044
10.3M
            }
1045
14.6M
            currentSearchNum++;
1046
14.6M
            i++;
1047
14.6M
        }
1048
1049
        // Swap the search and found pointers, copy the found hex count to the
1050
        // search hex count, and zero everything related to the found memory.
1051
46.1k
        H3Index *temp = search;
1052
46.1k
        search = found;
1053
46.1k
        found = temp;
1054
14.5M
        for (int64_t j = 0; j < numSearchHexes; j++) found[j] = 0;
1055
46.1k
        numSearchHexes = numFoundHexes;
1056
46.1k
        numFoundHexes = 0;
1057
        // Repeat until no new hexagons are found
1058
46.1k
    }
1059
    // The out memory structure should be complete, end it here
1060
429
    H3_MEMORY(free)(bboxes);
1061
429
    H3_MEMORY(free)(search);
1062
429
    H3_MEMORY(free)(found);
1063
429
    return E_SUCCESS;
1064
624
}
1065
1066
/**
1067
 * Internal: Create a vertex graph from a set of hexagons. It is the
1068
 * responsibility of the caller to call destroyVertexGraph on the populated
1069
 * graph, otherwise the memory in the graph nodes will not be freed.
1070
 * @private
1071
 * @param h3Set    Set of hexagons
1072
 * @param numHexes Number of hexagons in the set
1073
 * @param graph    Output graph
1074
 */
1075
H3Error h3SetToVertexGraph(const H3Index *h3Set, const int numHexes,
1076
0
                           VertexGraph *graph) {
1077
0
    CellBoundary vertices;
1078
0
    LatLng *fromVtx;
1079
0
    LatLng *toVtx;
1080
0
    VertexNode *edge;
1081
0
    if (numHexes < 1) {
1082
        // We still need to init the graph, or calls to destroyVertexGraph will
1083
        // fail
1084
0
        initVertexGraph(graph, 0, 0);
1085
0
        return E_SUCCESS;
1086
0
    }
1087
0
    int res = H3_GET_RESOLUTION(h3Set[0]);
1088
0
    const int minBuckets = 6;
1089
    // TODO: Better way to calculate/guess?
1090
0
    int numBuckets = numHexes > minBuckets ? numHexes : minBuckets;
1091
0
    initVertexGraph(graph, numBuckets, res);
1092
    // Iterate through every hexagon
1093
0
    for (int i = 0; i < numHexes; i++) {
1094
0
        H3Error boundaryErr = H3_EXPORT(cellToBoundary)(h3Set[i], &vertices);
1095
0
        if (boundaryErr) {
1096
            // Destroy vertex graph as caller will not know to do so.
1097
0
            destroyVertexGraph(graph);
1098
0
            return boundaryErr;
1099
0
        }
1100
        // iterate through every edge
1101
0
        for (int j = 0; j < vertices.numVerts; j++) {
1102
0
            fromVtx = &vertices.verts[j];
1103
0
            toVtx = &vertices.verts[(j + 1) % vertices.numVerts];
1104
            // If we've seen this edge already, it will be reversed
1105
0
            edge = findNodeForEdge(graph, toVtx, fromVtx);
1106
0
            if (edge != NULL) {
1107
                // If we've seen it, drop it. No edge is shared by more than 2
1108
                // hexagons, so we'll never see it again.
1109
0
                removeVertexNode(graph, edge);
1110
0
            } else {
1111
                // Add a new node for this edge
1112
0
                addVertexNode(graph, fromVtx, toVtx);
1113
0
            }
1114
0
        }
1115
0
    }
1116
0
    return E_SUCCESS;
1117
0
}
1118
1119
/**
1120
 * Internal: Create a LinkedGeoPolygon from a vertex graph. It is the
1121
 * responsibility of the caller to call destroyLinkedMultiPolygon on the
1122
 * populated linked geo structure, or the memory for that structure will not be
1123
 * freed.
1124
 * @private
1125
 * @param graph Input graph
1126
 * @param out   Output polygon
1127
 */
1128
0
void _vertexGraphToLinkedGeo(VertexGraph *graph, LinkedGeoPolygon *out) {
1129
0
    *out = (LinkedGeoPolygon){0};
1130
0
    LinkedGeoLoop *loop;
1131
0
    VertexNode *edge;
1132
0
    LatLng nextVtx;
1133
    // Find the next unused entry point
1134
0
    while ((edge = firstVertexNode(graph)) != NULL) {
1135
0
        loop = addNewLinkedLoop(out);
1136
        // Walk the graph to get the outline
1137
0
        do {
1138
0
            addLinkedCoord(loop, &edge->from);
1139
0
            nextVtx = edge->to;
1140
            // Remove frees the node, so we can't use edge after this
1141
0
            removeVertexNode(graph, edge);
1142
0
            edge = findNodeForVertex(graph, &nextVtx);
1143
0
        } while (edge);
1144
0
    }
1145
0
}
1146
1147
/**
1148
 * Create a LinkedGeoPolygon describing the outline(s) of a set of  hexagons.
1149
 * Polygon outlines will follow GeoJSON MultiPolygon order: Each polygon will
1150
 * have one outer loop, which is first in the list, followed by any holes.
1151
 *
1152
 * It is the responsibility of the caller to call destroyLinkedMultiPolygon on
1153
 * the populated linked geo structure, or the memory for that structure will not
1154
 * be freed.
1155
 *
1156
 * It is expected that all hexagons in the set have the same resolution and
1157
 * that the set contains no duplicates. Behavior is undefined if duplicates
1158
 * or multiple resolutions are present, and the algorithm may produce
1159
 * unexpected or invalid output.
1160
 *
1161
 * @param h3Set    Set of hexagons
1162
 * @param numHexes Number of hexagons in set
1163
 * @param out      Output polygon
1164
 */
1165
H3Error H3_EXPORT(cellsToLinkedMultiPolygon)(const H3Index *h3Set,
1166
                                             const int numHexes,
1167
0
                                             LinkedGeoPolygon *out) {
1168
0
    VertexGraph graph;
1169
0
    H3Error err = h3SetToVertexGraph(h3Set, numHexes, &graph);
1170
0
    if (err) {
1171
0
        return err;
1172
0
    }
1173
0
    _vertexGraphToLinkedGeo(&graph, out);
1174
0
    destroyVertexGraph(&graph);
1175
0
    H3Error normalizeResult = normalizeMultiPolygon(out);
1176
0
    if (normalizeResult) {
1177
0
        H3_EXPORT(destroyLinkedMultiPolygon)(out);
1178
0
    }
1179
0
    return normalizeResult;
1180
0
}