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