/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 | 77.9M | #define MAX_ONE_RING_SIZE 7 |
46 | 1.28k | #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 | 5.00k | H3Error H3_EXPORT(maxGridDiskSize)(int k, int64_t *out) { |
169 | 5.00k | if (k < 0) { |
170 | 0 | return E_DOMAIN; |
171 | 0 | } |
172 | 5.00k | 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 | 5.00k | *out = 3 * (int64_t)k * ((int64_t)k + 1) + 1; |
184 | 5.00k | return E_SUCCESS; |
185 | 5.00k | } |
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 | 9.74M | H3Error H3_EXPORT(gridDisk)(H3Index origin, int k, H3Index *out) { |
201 | 9.74M | return H3_EXPORT(gridDiskDistances)(origin, k, out, NULL); |
202 | 9.74M | } |
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 | 9.74M | int *distances) { |
223 | | // Optimistically try the faster gridDiskUnsafe algorithm first |
224 | 9.74M | const H3Error failed = |
225 | 9.74M | H3_EXPORT(gridDiskDistancesUnsafe)(origin, k, out, distances); |
226 | 9.74M | if (failed) { |
227 | 5.00k | int64_t maxIdx; |
228 | 5.00k | H3Error err = H3_EXPORT(maxGridDiskSize)(k, &maxIdx); |
229 | 5.00k | 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 | 5.00k | memset(out, 0, maxIdx * sizeof(H3Index)); |
235 | | |
236 | 5.00k | if (distances == NULL) { |
237 | 5.00k | distances = H3_MEMORY(calloc)(maxIdx, sizeof(int)); |
238 | 5.00k | if (!distances) { |
239 | 0 | return E_MEMORY_ALLOC; |
240 | 0 | } |
241 | 5.00k | H3Error result = _gridDiskDistancesInternal(origin, k, out, |
242 | 5.00k | distances, maxIdx, 0); |
243 | 5.00k | H3_MEMORY(free)(distances); |
244 | 5.00k | return result; |
245 | 5.00k | } else { |
246 | 0 | memset(distances, 0, maxIdx * sizeof(int)); |
247 | 0 | return _gridDiskDistancesInternal(origin, k, out, distances, maxIdx, |
248 | 0 | 0); |
249 | 0 | } |
250 | 9.73M | } else { |
251 | 9.73M | return E_SUCCESS; |
252 | 9.73M | } |
253 | 9.74M | } |
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 | 34.6k | int *distances, int64_t maxIdx, int curK) { |
274 | | // Put origin in the output array. out is used as a hash set. |
275 | 34.6k | int64_t off = origin % maxIdx; |
276 | 64.3k | while (out[off] != 0 && out[off] != origin) { |
277 | 29.6k | off = (off + 1) % maxIdx; |
278 | 29.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 | 34.6k | if (out[off] == origin && distances[off] <= curK) return E_SUCCESS; |
284 | | |
285 | 34.2k | out[off] = origin; |
286 | 34.2k | distances[off] = curK; |
287 | | |
288 | | // Base case: reached an index k away from the origin. |
289 | 34.2k | if (curK >= k) return E_SUCCESS; |
290 | | |
291 | | // Recurse to all neighbors in no particular order. |
292 | 35.0k | for (int i = 0; i < 6; i++) { |
293 | 30.0k | int rotations = 0; |
294 | 30.0k | H3Index nextNeighbor; |
295 | 30.0k | H3Error neighborResult = h3NeighborRotations(origin, DIRECTIONS[i], |
296 | 30.0k | &rotations, &nextNeighbor); |
297 | 30.0k | if (neighborResult != E_PENTAGON) { |
298 | | // E_PENTAGON is an expected case when trying to traverse off of |
299 | | // pentagons. |
300 | 29.6k | if (neighborResult != E_SUCCESS) { |
301 | 0 | return neighborResult; |
302 | 0 | } |
303 | 29.6k | neighborResult = _gridDiskDistancesInternal( |
304 | 29.6k | nextNeighbor, k, out, distances, maxIdx, curK + 1); |
305 | 29.6k | if (neighborResult) { |
306 | 0 | return neighborResult; |
307 | 0 | } |
308 | 29.6k | } |
309 | 30.0k | } |
310 | 5.00k | return E_SUCCESS; |
311 | 5.00k | } |
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 | 68.1M | H3Index *out) { |
354 | 68.1M | H3Index current = origin; |
355 | | |
356 | 68.1M | 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 | 68.1M | *rotations = *rotations % 6; |
362 | 69.0M | for (int i = 0; i < *rotations; i++) { |
363 | 875k | dir = _rotate60ccw(dir); |
364 | 875k | } |
365 | | |
366 | 68.1M | int newRotations = 0; |
367 | 68.1M | int oldBaseCell = H3_GET_BASE_CELL(current); |
368 | 68.1M | 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 | 68.1M | Direction oldLeadingDigit = _h3LeadingNonZeroDigit(current); |
373 | | |
374 | | // Adjust the indexing digits and, if needed, the base cell. |
375 | 68.1M | int r = H3_GET_RESOLUTION(current) - 1; |
376 | 118M | while (true) { |
377 | 118M | if (r == -1) { |
378 | 656k | H3_SET_BASE_CELL(current, baseCellNeighbors[oldBaseCell][dir]); |
379 | 656k | newRotations = baseCellNeighbor60CCWRots[oldBaseCell][dir]; |
380 | | |
381 | 656k | 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 | 3.26k | H3_SET_BASE_CELL(current, |
385 | 3.26k | baseCellNeighbors[oldBaseCell][IK_AXES_DIGIT]); |
386 | 3.26k | newRotations = |
387 | 3.26k | baseCellNeighbor60CCWRots[oldBaseCell][IK_AXES_DIGIT]; |
388 | | |
389 | | // perform the adjustment for the k-subsequence we're skipping |
390 | | // over. |
391 | 3.26k | current = _h3Rotate60ccw(current); |
392 | 3.26k | *rotations = *rotations + 1; |
393 | 3.26k | } |
394 | | |
395 | 656k | break; |
396 | 118M | } else { |
397 | 118M | Direction oldDigit = H3_GET_INDEX_DIGIT(current, r + 1); |
398 | 118M | Direction nextDir; |
399 | 118M | if (oldDigit == INVALID_DIGIT) { |
400 | | // Only possible on invalid input |
401 | 0 | return E_CELL_INVALID; |
402 | 118M | } else if (isResolutionClassIII(r + 1)) { |
403 | 73.3M | H3_SET_INDEX_DIGIT(current, r + 1, NEW_DIGIT_II[oldDigit][dir]); |
404 | 73.3M | nextDir = NEW_ADJUSTMENT_II[oldDigit][dir]; |
405 | 73.3M | } else { |
406 | 44.8M | H3_SET_INDEX_DIGIT(current, r + 1, |
407 | 44.8M | NEW_DIGIT_III[oldDigit][dir]); |
408 | 44.8M | nextDir = NEW_ADJUSTMENT_III[oldDigit][dir]; |
409 | 44.8M | } |
410 | | |
411 | 118M | if (nextDir != CENTER_DIGIT) { |
412 | 50.6M | dir = nextDir; |
413 | 50.6M | r--; |
414 | 67.5M | } else { |
415 | | // No more adjustment to perform |
416 | 67.5M | break; |
417 | 67.5M | } |
418 | 118M | } |
419 | 118M | } |
420 | | |
421 | 68.1M | int newBaseCell = H3_GET_BASE_CELL(current); |
422 | 68.1M | if (_isBaseCellPentagon(newBaseCell)) { |
423 | 4.73M | int alreadyAdjustedKSubsequence = 0; |
424 | | |
425 | | // force rotation out of missing k-axes sub-sequence |
426 | 4.73M | if (_h3LeadingNonZeroDigit(current) == K_AXES_DIGIT) { |
427 | 15.7k | 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 | 3.98k | if (ALWAYS(_baseCellIsCwOffset( |
435 | 3.98k | newBaseCell, |
436 | 3.98k | baseCellData[oldBaseCell].homeFijk.face))) { |
437 | 3.98k | current = _h3Rotate60cw(current); |
438 | 3.98k | } 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 | 11.7k | } else { |
445 | | // In this case, we traversed into the deleted |
446 | | // k subsequence from within the same pentagon |
447 | | // base cell. |
448 | 11.7k | if (oldLeadingDigit == CENTER_DIGIT) { |
449 | | // Undefined: the k direction is deleted from here |
450 | 365 | return E_PENTAGON; |
451 | 11.4k | } 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 | 5.94k | current = _h3Rotate60ccw(current); |
456 | 5.94k | *rotations = *rotations + 1; |
457 | 5.94k | } 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 | 5.48k | current = _h3Rotate60cw(current); |
462 | 5.48k | *rotations = *rotations + 5; |
463 | 5.48k | } else { |
464 | | // TODO: Should never occur, but is reachable by fuzzer |
465 | 0 | return E_FAILED; |
466 | 0 | } |
467 | 11.7k | } |
468 | 15.7k | } |
469 | | |
470 | 4.81M | for (int i = 0; i < newRotations; i++) |
471 | 79.1k | 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 | 4.73M | if (oldBaseCell != newBaseCell) { |
476 | 52.7k | if (_isBaseCellPolarPentagon(newBaseCell)) { |
477 | | // 'polar' base cells behave differently because they have all |
478 | | // i neighbors. |
479 | 15.8k | if (oldBaseCell != 118 && oldBaseCell != 8 && |
480 | 15.8k | _h3LeadingNonZeroDigit(current) != JK_AXES_DIGIT) { |
481 | 11.5k | *rotations = *rotations + 1; |
482 | 11.5k | } |
483 | 36.9k | } else if (_h3LeadingNonZeroDigit(current) == IK_AXES_DIGIT && |
484 | 36.9k | !alreadyAdjustedKSubsequence) { |
485 | | // account for distortion introduced to the 5 neighbor by the |
486 | | // deleted k subsequence. |
487 | 4.25k | *rotations = *rotations + 1; |
488 | 4.25k | } |
489 | 52.7k | } |
490 | 63.4M | } else { |
491 | 64.4M | for (int i = 0; i < newRotations; i++) |
492 | 940k | current = _h3Rotate60ccw(current); |
493 | 63.4M | } |
494 | | |
495 | 68.1M | *rotations = (*rotations + newRotations) % 6; |
496 | 68.1M | *out = current; |
497 | | |
498 | 68.1M | return E_SUCCESS; |
499 | 68.1M | } |
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 | 9.74M | 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 | 9.74M | if (k < 0) { |
574 | 0 | return E_DOMAIN; |
575 | 0 | } |
576 | | |
577 | | // k must be >= 0, so origin is always needed |
578 | 9.74M | int idx = 0; |
579 | 9.74M | out[idx] = origin; |
580 | 9.74M | if (distances) { |
581 | 0 | distances[idx] = 0; |
582 | 0 | } |
583 | 9.74M | idx++; |
584 | | |
585 | 9.74M | if (H3_EXPORT(isPentagon)(origin)) { |
586 | | // Pentagon was encountered; bail out as user doesn't want this. |
587 | 808 | return E_PENTAGON; |
588 | 808 | } |
589 | | |
590 | | // 0 < ring <= k, current ring |
591 | 9.74M | int ring = 1; |
592 | | // 0 <= direction < 6, current side of the ring |
593 | 9.74M | int direction = 0; |
594 | | // 0 <= i < ring, current position on the side of the ring |
595 | 9.74M | int i = 0; |
596 | | // Number of 60 degree ccw rotations to perform on the direction (based on |
597 | | // which faces have been crossed.) |
598 | 9.74M | int rotations = 0; |
599 | | |
600 | 68.1M | while (ring <= k) { |
601 | 58.4M | 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 | 9.74M | H3Error neighborResult = h3NeighborRotations( |
605 | 9.74M | origin, NEXT_RING_DIRECTION, &rotations, &origin); |
606 | 9.74M | 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 | 9.74M | if (H3_EXPORT(isPentagon)(origin)) { |
614 | | // Pentagon was encountered; bail out as user doesn't want this. |
615 | 1.27k | return E_PENTAGON; |
616 | 1.27k | } |
617 | 9.74M | } |
618 | | |
619 | 58.4M | H3Error neighborResult = h3NeighborRotations( |
620 | 58.4M | origin, DIRECTIONS[direction], &rotations, &origin); |
621 | 58.4M | if (neighborResult) { |
622 | 0 | return neighborResult; |
623 | 0 | } |
624 | 58.4M | out[idx] = origin; |
625 | 58.4M | if (distances) { |
626 | 0 | distances[idx] = ring; |
627 | 0 | } |
628 | 58.4M | idx++; |
629 | | |
630 | 58.4M | i++; |
631 | | // Check if end of this side of the k-ring |
632 | 58.4M | if (i == ring) { |
633 | 58.4M | i = 0; |
634 | 58.4M | direction++; |
635 | | // Check if end of this ring. |
636 | 58.4M | if (direction == 6) { |
637 | 9.73M | direction = 0; |
638 | 9.73M | ring++; |
639 | 9.73M | } |
640 | 58.4M | } |
641 | | |
642 | 58.4M | if (H3_EXPORT(isPentagon)(origin)) { |
643 | | // Pentagon was encountered; bail out as user doesn't want this. |
644 | 2.92k | return E_PENTAGON; |
645 | 2.92k | } |
646 | 58.4M | } |
647 | 9.73M | return E_SUCCESS; |
648 | 9.74M | } |
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.32k | uint32_t flags, int64_t *out) { |
777 | 1.32k | if (flags != 0) { |
778 | 0 | return E_OPTION_INVALID; |
779 | 0 | } |
780 | | // Get the bounding box for the GeoJSON-like struct |
781 | 1.32k | BBox bbox; |
782 | 1.32k | const GeoLoop geoloop = geoPolygon->geoloop; |
783 | 1.32k | bboxFromGeoLoop(&geoloop, &bbox); |
784 | 1.32k | int64_t numHexagons; |
785 | 1.32k | H3Error estimateErr = bboxHexEstimate(&bbox, res, &numHexagons); |
786 | 1.32k | if (estimateErr) { |
787 | 37 | return estimateErr; |
788 | 37 | } |
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.28k | int totalVerts = geoloop.numVerts; |
793 | 1.28k | for (int i = 0; i < geoPolygon->numHoles; i++) { |
794 | 0 | totalVerts += geoPolygon->holes[i].numVerts; |
795 | 0 | } |
796 | 1.28k | 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.28k | numHexagons += POLYGON_TO_CELLS_BUFFER; |
802 | 1.28k | *out = numHexagons; |
803 | 1.28k | return E_SUCCESS; |
804 | 1.32k | } |
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 | 615 | H3Index *found) { |
827 | 559k | for (int i = 0; i < geoloop->numVerts; i++) { |
828 | 559k | LatLng origin = geoloop->verts[i]; |
829 | 559k | LatLng destination = i == geoloop->numVerts - 1 ? geoloop->verts[0] |
830 | 559k | : geoloop->verts[i + 1]; |
831 | 559k | int64_t numHexesEstimate; |
832 | 559k | H3Error estimateErr = |
833 | 559k | lineHexEstimate(&origin, &destination, res, &numHexesEstimate); |
834 | 559k | if (estimateErr) { |
835 | 22 | return estimateErr; |
836 | 22 | } |
837 | 13.0M | for (int64_t j = 0; j < numHexesEstimate; j++) { |
838 | 12.4M | LatLng interpolate; |
839 | 12.4M | interpolate.lat = |
840 | 12.4M | (origin.lat * (numHexesEstimate - j) / numHexesEstimate) + |
841 | 12.4M | (destination.lat * j / numHexesEstimate); |
842 | 12.4M | interpolate.lng = |
843 | 12.4M | (origin.lng * (numHexesEstimate - j) / numHexesEstimate) + |
844 | 12.4M | (destination.lng * j / numHexesEstimate); |
845 | 12.4M | H3Index pointHex; |
846 | 12.4M | H3Error e = H3_EXPORT(latLngToCell)(&interpolate, res, &pointHex); |
847 | 12.4M | if (e) { |
848 | 19 | return e; |
849 | 19 | } |
850 | | // A simple hash to store the hexagon, or move to another place if |
851 | | // needed |
852 | 12.4M | int64_t loc = (int64_t)(pointHex % numHexagons); |
853 | 12.4M | int64_t loopCount = 0; |
854 | 834M | 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 | 826M | if (loopCount > numHexagons) return E_FAILED; |
859 | 826M | if (found[loc] == pointHex) |
860 | 4.17M | break; // At least two points of the geoloop index to the |
861 | | // same cell |
862 | 821M | loc = (loc + 1) % numHexagons; |
863 | 821M | loopCount++; |
864 | 821M | } |
865 | 12.4M | if (found[loc] == pointHex) |
866 | 4.17M | continue; // Skip this hex, already exists in the found hash |
867 | | // Otherwise, set it in the found hash for now |
868 | 8.31M | found[loc] = pointHex; |
869 | | |
870 | 8.31M | search[*numSearchHexes] = pointHex; |
871 | 8.31M | (*numSearchHexes)++; |
872 | 8.31M | } |
873 | 559k | } |
874 | 546 | return E_SUCCESS; |
875 | 615 | } |
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 | 615 | uint32_t flags, H3Index *out) { |
893 | 615 | 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 | 615 | BBox *bboxes = H3_MEMORY(malloc)((geoPolygon->numHoles + 1) * sizeof(BBox)); |
915 | 615 | if (!bboxes) { |
916 | 0 | return E_MEMORY_ALLOC; |
917 | 0 | } |
918 | 615 | bboxesFromGeoPolygon(geoPolygon, bboxes); |
919 | | |
920 | | // Get the estimated number of hexagons and allocate some temporary memory |
921 | | // for the hexagons |
922 | 615 | int64_t numHexagons; |
923 | 615 | H3Error numHexagonsError = |
924 | 615 | H3_EXPORT(maxPolygonToCellsSize)(geoPolygon, res, flags, &numHexagons); |
925 | 615 | if (numHexagonsError) { |
926 | 0 | H3_MEMORY(free)(bboxes); |
927 | 0 | return numHexagonsError; |
928 | 0 | } |
929 | 615 | H3Index *search = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); |
930 | 615 | if (!search) { |
931 | 0 | H3_MEMORY(free)(bboxes); |
932 | 0 | return E_MEMORY_ALLOC; |
933 | 0 | } |
934 | 615 | H3Index *found = H3_MEMORY(calloc)(numHexagons, sizeof(H3Index)); |
935 | 615 | 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 | 615 | int64_t numSearchHexes = 0; |
944 | 615 | 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 | 615 | const GeoLoop geoloop = geoPolygon->geoloop; |
951 | 615 | H3Error edgeHexError = _getEdgeHexagons(&geoloop, numHexagons, res, |
952 | 615 | &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 | 615 | if (edgeHexError) { |
957 | 69 | H3_MEMORY(free)(search); |
958 | 69 | H3_MEMORY(free)(found); |
959 | 69 | H3_MEMORY(free)(bboxes); |
960 | 69 | return edgeHexError; |
961 | 69 | } |
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 | 546 | for (int i = 0; i < geoPolygon->numHoles; i++) { |
969 | 0 | GeoLoop *hole = &(geoPolygon->holes[i]); |
970 | 0 | edgeHexError = _getEdgeHexagons(hole, numHexagons, res, &numSearchHexes, |
971 | 0 | 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 | 0 | if (edgeHexError) { |
976 | 0 | H3_MEMORY(free)(search); |
977 | 0 | H3_MEMORY(free)(found); |
978 | 0 | H3_MEMORY(free)(bboxes); |
979 | 0 | return edgeHexError; |
980 | 0 | } |
981 | 0 | } |
982 | | |
983 | | // 3. Re-zero the found hash so it can be used in the main loop below |
984 | 310M | 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 | 3.35k | 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 | 2.85k | int64_t currentSearchNum = 0; |
992 | 2.85k | int64_t i = 0; |
993 | 9.74M | while (currentSearchNum < numSearchHexes) { |
994 | 9.74M | H3Index ring[MAX_ONE_RING_SIZE] = {0}; |
995 | 9.74M | H3Index searchHex = search[i]; |
996 | 9.74M | H3_EXPORT(gridDisk)(searchHex, 1, ring); |
997 | 77.9M | for (int j = 0; j < MAX_ONE_RING_SIZE; j++) { |
998 | 68.1M | if (ring[j] == H3_NULL) { |
999 | 808 | continue; // Skip if this was a pentagon and only had 5 |
1000 | | // neighbors |
1001 | 808 | } |
1002 | | |
1003 | 68.1M | 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 | 68.1M | int64_t loc = (int64_t)(hex % numHexagons); |
1009 | 68.1M | int64_t loopCount = 0; |
1010 | 1.23G | 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 | 1.19G | if (loopCount > numHexagons) { |
1016 | 53 | H3_MEMORY(free)(search); |
1017 | 53 | H3_MEMORY(free)(found); |
1018 | 53 | H3_MEMORY(free)(bboxes); |
1019 | 53 | return E_FAILED; |
1020 | 53 | } |
1021 | 1.19G | if (out[loc] == hex) break; // Skip duplicates found |
1022 | 1.17G | loc = (loc + 1) % numHexagons; |
1023 | 1.17G | loopCount++; |
1024 | 1.17G | } |
1025 | 68.1M | if (out[loc] == hex) { |
1026 | 25.0M | continue; // Skip this hex, already exists in the out hash |
1027 | 25.0M | } |
1028 | | |
1029 | | // Check if the hexagon is in the polygon or not |
1030 | 43.1M | LatLng hexCenter; |
1031 | 43.1M | H3_EXPORT(cellToLatLng)(hex, &hexCenter); |
1032 | | |
1033 | | // If not, skip |
1034 | 43.1M | if (!pointInsidePolygon(geoPolygon, bboxes, &hexCenter)) { |
1035 | 37.9M | continue; |
1036 | 37.9M | } |
1037 | | |
1038 | | // Otherwise set it in the output array |
1039 | 5.17M | out[loc] = hex; |
1040 | | |
1041 | | // Set the hexagon in the found hash |
1042 | 5.17M | found[numFoundHexes] = hex; |
1043 | 5.17M | numFoundHexes++; |
1044 | 5.17M | } |
1045 | 9.74M | currentSearchNum++; |
1046 | 9.74M | i++; |
1047 | 9.74M | } |
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 | 2.80k | H3Index *temp = search; |
1052 | 2.80k | search = found; |
1053 | 2.80k | found = temp; |
1054 | 9.35M | for (int64_t j = 0; j < numSearchHexes; j++) found[j] = 0; |
1055 | 2.80k | numSearchHexes = numFoundHexes; |
1056 | 2.80k | numFoundHexes = 0; |
1057 | | // Repeat until no new hexagons are found |
1058 | 2.80k | } |
1059 | | // The out memory structure should be complete, end it here |
1060 | 493 | H3_MEMORY(free)(bboxes); |
1061 | 493 | H3_MEMORY(free)(search); |
1062 | 493 | H3_MEMORY(free)(found); |
1063 | 493 | return E_SUCCESS; |
1064 | 546 | } |
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 | } |