/src/h3/src/h3lib/lib/linkedGeo.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2017-2018, 2020-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 linkedGeo.c |
17 | | * @brief Linked data structure for geo data |
18 | | */ |
19 | | |
20 | | #include "linkedGeo.h" |
21 | | |
22 | | #include <assert.h> |
23 | | #include <stdlib.h> |
24 | | |
25 | | #include "alloc.h" |
26 | | #include "cellsToMultiPoly.h" |
27 | | #include "h3Assert.h" |
28 | | #include "h3api.h" |
29 | | #include "polygon.h" |
30 | | |
31 | | /** |
32 | | * Add a linked polygon to the current polygon |
33 | | * @param polygon Polygon to add link to |
34 | | * @return Pointer to new polygon |
35 | | */ |
36 | 0 | LinkedGeoPolygon *addNewLinkedPolygon(LinkedGeoPolygon *polygon) { |
37 | 0 | assert(polygon->next == NULL); |
38 | 0 | LinkedGeoPolygon *next = H3_MEMORY(calloc)(1, sizeof(*next)); |
39 | 0 | assert(next != NULL); |
40 | 0 | polygon->next = next; |
41 | 0 | return next; |
42 | 0 | } |
43 | | |
44 | | /** |
45 | | * Add a new linked loop to the current polygon |
46 | | * @param polygon Polygon to add loop to |
47 | | * @return Pointer to loop |
48 | | */ |
49 | 0 | LinkedGeoLoop *addNewLinkedLoop(LinkedGeoPolygon *polygon) { |
50 | 0 | LinkedGeoLoop *loop = H3_MEMORY(calloc)(1, sizeof(*loop)); |
51 | 0 | assert(loop != NULL); |
52 | 0 | return addLinkedLoop(polygon, loop); |
53 | 0 | } |
54 | | |
55 | | /** |
56 | | * Add an existing linked loop to the current polygon |
57 | | * @param polygon Polygon to add loop to |
58 | | * @return Pointer to loop |
59 | | */ |
60 | 0 | LinkedGeoLoop *addLinkedLoop(LinkedGeoPolygon *polygon, LinkedGeoLoop *loop) { |
61 | 0 | LinkedGeoLoop *last = polygon->last; |
62 | 0 | if (last == NULL) { |
63 | 0 | assert(polygon->first == NULL); |
64 | 0 | polygon->first = loop; |
65 | 0 | } else { |
66 | 0 | last->next = loop; |
67 | 0 | } |
68 | 0 | polygon->last = loop; |
69 | 0 | return loop; |
70 | 0 | } |
71 | | |
72 | | /** |
73 | | * Add a new linked coordinate to the current loop |
74 | | * @param loop Loop to add coordinate to |
75 | | * @param vertex Coordinate to add |
76 | | * @return Pointer to the coordinate |
77 | | */ |
78 | 0 | LinkedLatLng *addLinkedCoord(LinkedGeoLoop *loop, const LatLng *vertex) { |
79 | 0 | LinkedLatLng *coord = H3_MEMORY(malloc)(sizeof(*coord)); |
80 | 0 | assert(coord != NULL); |
81 | 0 | *coord = (LinkedLatLng){.vertex = *vertex, .next = NULL}; |
82 | 0 | LinkedLatLng *last = loop->last; |
83 | 0 | if (last == NULL) { |
84 | 0 | assert(loop->first == NULL); |
85 | 0 | loop->first = coord; |
86 | 0 | } else { |
87 | 0 | last->next = coord; |
88 | 0 | } |
89 | 0 | loop->last = coord; |
90 | 0 | return coord; |
91 | 0 | } |
92 | | |
93 | | /** |
94 | | * Free all allocated memory for a linked geo loop. The caller is |
95 | | * responsible for freeing memory allocated to input loop struct. |
96 | | * @param loop Loop to free |
97 | | */ |
98 | 0 | void destroyLinkedGeoLoop(LinkedGeoLoop *loop) { |
99 | 0 | LinkedLatLng *nextCoord; |
100 | 0 | for (LinkedLatLng *currentCoord = loop->first; currentCoord != NULL; |
101 | 0 | currentCoord = nextCoord) { |
102 | 0 | nextCoord = currentCoord->next; |
103 | 0 | H3_MEMORY(free)(currentCoord); |
104 | 0 | } |
105 | 0 | } |
106 | | |
107 | | /** |
108 | | * Free all allocated memory for a linked geo structure. The caller is |
109 | | * responsible for freeing memory allocated to input polygon struct. |
110 | | * @param polygon Pointer to the first polygon in the structure |
111 | | */ |
112 | 0 | void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon) { |
113 | | // flag to skip the input polygon |
114 | 0 | bool skip = true; |
115 | 0 | LinkedGeoPolygon *nextPolygon; |
116 | 0 | LinkedGeoLoop *nextLoop; |
117 | 0 | for (LinkedGeoPolygon *currentPolygon = polygon; currentPolygon != NULL; |
118 | 0 | currentPolygon = nextPolygon) { |
119 | 0 | for (LinkedGeoLoop *currentLoop = currentPolygon->first; |
120 | 0 | currentLoop != NULL; currentLoop = nextLoop) { |
121 | 0 | destroyLinkedGeoLoop(currentLoop); |
122 | 0 | nextLoop = currentLoop->next; |
123 | 0 | H3_MEMORY(free)(currentLoop); |
124 | 0 | } |
125 | 0 | nextPolygon = currentPolygon->next; |
126 | 0 | if (skip) { |
127 | | // do not free the input polygon, but zero it so this |
128 | | // function is idempotent (safe to call twice) |
129 | 0 | *polygon = (LinkedGeoPolygon){0}; |
130 | 0 | skip = false; |
131 | 0 | } else { |
132 | 0 | H3_MEMORY(free)(currentPolygon); |
133 | 0 | } |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | | /** |
138 | | * Count the number of polygons in a linked list |
139 | | * @param polygon Starting polygon |
140 | | * @return Count |
141 | | */ |
142 | 0 | int countLinkedPolygons(const LinkedGeoPolygon *polygon) { |
143 | 0 | int count = 0; |
144 | 0 | while (polygon != NULL) { |
145 | 0 | count++; |
146 | 0 | polygon = polygon->next; |
147 | 0 | } |
148 | 0 | return count; |
149 | 0 | } |
150 | | |
151 | | /** |
152 | | * Count the number of linked loops in a polygon |
153 | | * @param polygon Polygon to count loops for |
154 | | * @return Count |
155 | | */ |
156 | 0 | int countLinkedLoops(const LinkedGeoPolygon *polygon) { |
157 | 0 | LinkedGeoLoop *loop = polygon->first; |
158 | 0 | int count = 0; |
159 | 0 | while (loop != NULL) { |
160 | 0 | count++; |
161 | 0 | loop = loop->next; |
162 | 0 | } |
163 | 0 | return count; |
164 | 0 | } |
165 | | |
166 | | /** |
167 | | * Count the number of coordinates in a loop |
168 | | * @param loop Loop to count coordinates for |
169 | | * @return Count |
170 | | */ |
171 | 0 | int countLinkedCoords(const LinkedGeoLoop *loop) { |
172 | 0 | LinkedLatLng *coord = loop->first; |
173 | 0 | int count = 0; |
174 | 0 | while (coord != NULL) { |
175 | 0 | count++; |
176 | 0 | coord = coord->next; |
177 | 0 | } |
178 | 0 | return count; |
179 | 0 | } |
180 | | |
181 | | /** |
182 | | * Convert a LinkedGeoLoop to a GeoLoop by counting coords and copying. |
183 | | * @param linked Source LinkedGeoLoop |
184 | | * @param out Output GeoLoop (verts will be allocated) |
185 | | * @return E_SUCCESS, E_FAILED (< 3 verts), or E_MEMORY_ALLOC |
186 | | */ |
187 | | static H3Error linkedGeoLoopToGeoLoop(const LinkedGeoLoop *linked, |
188 | 0 | GeoLoop *out) { |
189 | 0 | int numVerts = countLinkedCoords(linked); |
190 | 0 | if (numVerts < 3) { |
191 | 0 | return E_FAILED; |
192 | 0 | } |
193 | 0 | LatLng *verts = H3_MEMORY(calloc)(numVerts, sizeof(LatLng)); |
194 | 0 | if (!verts) return E_MEMORY_ALLOC; |
195 | | |
196 | 0 | LinkedLatLng *coord = linked->first; |
197 | 0 | for (int i = 0; coord != NULL && ALWAYS(i < numVerts); i++) { |
198 | 0 | verts[i] = coord->vertex; |
199 | 0 | coord = coord->next; |
200 | 0 | } |
201 | 0 | out->numVerts = numVerts; |
202 | 0 | out->verts = verts; |
203 | 0 | return E_SUCCESS; |
204 | 0 | } |
205 | | |
206 | | /** |
207 | | * Convert a single LinkedGeoPolygon (outer loop + holes) to a GeoPolygon. |
208 | | * The output's geoloop and holes are allocated; on failure, all partial |
209 | | * allocations are freed and `out` is left in a clean (zeroed) state. |
210 | | * @param linked Source LinkedGeoPolygon |
211 | | * @param out Output GeoPolygon (caller-owned, will be populated) |
212 | | * @return E_SUCCESS or E_MEMORY_ALLOC |
213 | | */ |
214 | | static H3Error linkedGeoPolygonToGeoPolygon(const LinkedGeoPolygon *linked, |
215 | 0 | GeoPolygon *out) { |
216 | | // Convert outer loop |
217 | 0 | const LinkedGeoLoop *firstLoop = linked->first; |
218 | 0 | H3Error err = linkedGeoLoopToGeoLoop(firstLoop, &out->geoloop); |
219 | 0 | if (err) return err; |
220 | | |
221 | | // Count and convert holes |
222 | 0 | int numHoles = countLinkedLoops(linked) - 1; |
223 | 0 | if (numHoles > 0) { |
224 | 0 | GeoLoop *holes = H3_MEMORY(calloc)(numHoles, sizeof(GeoLoop)); |
225 | 0 | if (!holes) { |
226 | 0 | destroyGeoPolygon(out); |
227 | 0 | return E_MEMORY_ALLOC; |
228 | 0 | } |
229 | 0 | out->holes = holes; |
230 | 0 | out->numHoles = numHoles; |
231 | |
|
232 | 0 | LinkedGeoLoop *loop = firstLoop->next; |
233 | 0 | for (int i = 0; loop != NULL && ALWAYS(i < numHoles); i++) { |
234 | 0 | err = linkedGeoLoopToGeoLoop(loop, &holes[i]); |
235 | 0 | if (err) { |
236 | 0 | destroyGeoPolygon(out); |
237 | 0 | return err; |
238 | 0 | } |
239 | 0 | loop = loop->next; |
240 | 0 | } |
241 | 0 | } |
242 | | |
243 | 0 | return E_SUCCESS; |
244 | 0 | } |
245 | | |
246 | | /** |
247 | | * Convert a LinkedGeoPolygon to a GeoMultiPolygon. |
248 | | * |
249 | | * An empty chain (head node with no loops and no `next`) produces an empty |
250 | | * output (numPolygons=0) and returns E_SUCCESS. Every non-empty polygon |
251 | | * node must have an outer loop, and every loop must have >= 3 vertices; |
252 | | * otherwise, E_FAILED is returned. |
253 | | * |
254 | | * On error, any (partial) output is cleaned up via `destroyGeoMultiPolygon()`. |
255 | | * On success the caller owns the output and must free |
256 | | * it with `destroyGeoMultiPolygon()`. |
257 | | * |
258 | | * @param linked Head of LinkedGeoPolygon chain |
259 | | * @param out Output GeoMultiPolygon (caller-owned, will be populated) |
260 | | * @return E_SUCCESS, E_FAILED (invalid geometry), or E_MEMORY_ALLOC |
261 | | */ |
262 | | H3Error linkedGeoPolygonToGeoMultiPolygon(const LinkedGeoPolygon *linked, |
263 | 0 | GeoMultiPolygon *out) { |
264 | 0 | out->numPolygons = 0; |
265 | 0 | out->polygons = NULL; |
266 | | |
267 | | // Empty chain (head has no loops and no next) is valid: 0 polygons |
268 | 0 | if (linked->first == NULL && linked->next == NULL) { |
269 | 0 | return E_SUCCESS; |
270 | 0 | } |
271 | | |
272 | 0 | int numPolygons = countLinkedPolygons(linked); |
273 | |
|
274 | 0 | GeoPolygon *polygons = H3_MEMORY(calloc)(numPolygons, sizeof(GeoPolygon)); |
275 | 0 | if (!polygons) return E_MEMORY_ALLOC; |
276 | | |
277 | 0 | out->polygons = polygons; |
278 | 0 | out->numPolygons = numPolygons; |
279 | |
|
280 | 0 | const LinkedGeoPolygon *lpoly = linked; |
281 | 0 | for (int i = 0; lpoly != NULL && ALWAYS(i < numPolygons); i++) { |
282 | 0 | if (lpoly->first == NULL) { |
283 | 0 | H3_EXPORT(destroyGeoMultiPolygon)(out); |
284 | 0 | return E_FAILED; |
285 | 0 | } |
286 | 0 | H3Error err = linkedGeoPolygonToGeoPolygon(lpoly, &polygons[i]); |
287 | 0 | if (err) { |
288 | 0 | H3_EXPORT(destroyGeoMultiPolygon)(out); |
289 | 0 | return err; |
290 | 0 | } |
291 | 0 | lpoly = lpoly->next; |
292 | 0 | } |
293 | | |
294 | 0 | return E_SUCCESS; |
295 | 0 | } |
296 | | |
297 | | /** |
298 | | * Populate a LinkedGeoLoop with vertices from a GeoLoop. |
299 | | * @param src Source GeoLoop |
300 | | * @param loop Target LinkedGeoLoop (must be calloc-zeroed) |
301 | | * @return E_SUCCESS or E_MEMORY_ALLOC |
302 | | */ |
303 | 0 | static H3Error geoLoopToLinkedGeoLoop(const GeoLoop *src, LinkedGeoLoop *loop) { |
304 | 0 | if (src->numVerts < 3) return E_FAILED; |
305 | 0 | for (int i = 0; i < src->numVerts; i++) { |
306 | 0 | LinkedLatLng *coord = H3_MEMORY(malloc)(sizeof(LinkedLatLng)); |
307 | 0 | if (!coord) return E_MEMORY_ALLOC; |
308 | | |
309 | 0 | *coord = (LinkedLatLng){.vertex = src->verts[i], .next = NULL}; |
310 | 0 | if (loop->last == NULL) { |
311 | 0 | loop->first = coord; |
312 | 0 | } else { |
313 | 0 | loop->last->next = coord; |
314 | 0 | } |
315 | 0 | loop->last = coord; |
316 | 0 | } |
317 | 0 | return E_SUCCESS; |
318 | 0 | } |
319 | | |
320 | | /** |
321 | | * Convert a single GeoPolygon to LinkedGeoLoops within a LinkedGeoPolygon. |
322 | | * @param poly Source GeoPolygon |
323 | | * @param currentPoly Target LinkedGeoPolygon to populate |
324 | | * @return E_SUCCESS or E_MEMORY_ALLOC |
325 | | */ |
326 | | static H3Error addLinkedGeoLoop(const GeoLoop *gl, |
327 | 0 | LinkedGeoPolygon *currentPoly) { |
328 | 0 | LinkedGeoLoop *loop = H3_MEMORY(calloc)(1, sizeof(LinkedGeoLoop)); |
329 | 0 | if (!loop) return E_MEMORY_ALLOC; |
330 | | |
331 | 0 | if (currentPoly->last) { |
332 | 0 | currentPoly->last->next = loop; |
333 | 0 | } else { |
334 | 0 | currentPoly->first = loop; |
335 | 0 | } |
336 | 0 | currentPoly->last = loop; |
337 | |
|
338 | 0 | return geoLoopToLinkedGeoLoop(gl, loop); |
339 | 0 | } |
340 | | |
341 | | static H3Error geoPolygonToLinkedGeoLoops(const GeoPolygon *poly, |
342 | 0 | LinkedGeoPolygon *currentPoly) { |
343 | 0 | H3Error err = addLinkedGeoLoop(&poly->geoloop, currentPoly); |
344 | 0 | if (err) return err; |
345 | | |
346 | 0 | for (int i = 0; i < poly->numHoles; i++) { |
347 | 0 | err = addLinkedGeoLoop(&poly->holes[i], currentPoly); |
348 | 0 | if (err) return err; |
349 | 0 | } |
350 | | |
351 | 0 | return E_SUCCESS; |
352 | 0 | } |
353 | | |
354 | | /** |
355 | | * Convert a GeoMultiPolygon to a LinkedGeoPolygon. |
356 | | * |
357 | | * The first polygon is placed in the caller-owned `out` node. |
358 | | * Every loop must have >= 3 vertices; otherwise E_FAILED is returned. |
359 | | * |
360 | | * On error, the output is cleaned up via `destroyLinkedMultiPolygon()`. |
361 | | * On success, the caller owns the output and must free it with |
362 | | * `destroyLinkedMultiPolygon()`. |
363 | | * |
364 | | * @param mpoly Source GeoMultiPolygon |
365 | | * @param out Output LinkedGeoPolygon |
366 | | * @return E_SUCCESS, E_FAILED (invalid geometry), or E_MEMORY_ALLOC |
367 | | */ |
368 | | H3Error geoMultiPolygonToLinkedGeoPolygon(const GeoMultiPolygon *mpoly, |
369 | 0 | LinkedGeoPolygon *out) { |
370 | 0 | *out = (LinkedGeoPolygon){0}; |
371 | |
|
372 | 0 | LinkedGeoPolygon *currentPoly = out; |
373 | 0 | for (int i = 0; i < mpoly->numPolygons; i++) { |
374 | 0 | if (i > 0) { |
375 | 0 | LinkedGeoPolygon *newPoly = |
376 | 0 | H3_MEMORY(calloc)(1, sizeof(LinkedGeoPolygon)); |
377 | 0 | if (!newPoly) { |
378 | 0 | H3_EXPORT(destroyLinkedMultiPolygon)(out); |
379 | 0 | return E_MEMORY_ALLOC; |
380 | 0 | } |
381 | 0 | currentPoly->next = newPoly; |
382 | 0 | currentPoly = newPoly; |
383 | 0 | } |
384 | | |
385 | 0 | H3Error err = |
386 | 0 | geoPolygonToLinkedGeoLoops(&mpoly->polygons[i], currentPoly); |
387 | 0 | if (err) { |
388 | 0 | H3_EXPORT(destroyLinkedMultiPolygon)(out); |
389 | 0 | return err; |
390 | 0 | } |
391 | 0 | } |
392 | | |
393 | 0 | return E_SUCCESS; |
394 | 0 | } |
395 | | |
396 | | /** |
397 | | * Count the number of polygons containing a given loop. |
398 | | * @param loop Loop to count containers for |
399 | | * @param polygons Polygons to test |
400 | | * @param bboxes Bounding boxes for polygons, used in point-in-poly check |
401 | | * @param polygonCount Number of polygons in the test array |
402 | | * @return Number of polygons containing the loop |
403 | | */ |
404 | | static int countContainers(const LinkedGeoLoop *loop, |
405 | | const LinkedGeoPolygon **polygons, |
406 | 0 | const BBox **bboxes, const int polygonCount) { |
407 | 0 | int containerCount = 0; |
408 | 0 | for (int i = 0; i < polygonCount; i++) { |
409 | 0 | if (loop != polygons[i]->first && |
410 | 0 | pointInsideLinkedGeoLoop(polygons[i]->first, bboxes[i], |
411 | 0 | &loop->first->vertex)) { |
412 | 0 | containerCount++; |
413 | 0 | } |
414 | 0 | } |
415 | 0 | return containerCount; |
416 | 0 | } |
417 | | |
418 | | /** |
419 | | * Given a list of nested containers, find the one most deeply nested. |
420 | | * @param polygons Polygon containers to check |
421 | | * @param bboxes Bounding boxes for polygons, used in point-in-poly check |
422 | | * @param polygonCount Number of polygons in the list |
423 | | * @return Deepest container, or null if list is empty |
424 | | */ |
425 | | static const LinkedGeoPolygon *findDeepestContainer( |
426 | | const LinkedGeoPolygon **polygons, const BBox **bboxes, |
427 | 0 | const int polygonCount) { |
428 | | // Set the initial return value to the first candidate |
429 | 0 | const LinkedGeoPolygon *parent = polygonCount > 0 ? polygons[0] : NULL; |
430 | | |
431 | | // If we have multiple polygons, they must be nested inside each other. |
432 | | // Find the innermost polygon by taking the one with the most containers |
433 | | // in the list. |
434 | 0 | if (polygonCount > 1) { |
435 | 0 | int max = -1; |
436 | 0 | for (int i = 0; i < polygonCount; i++) { |
437 | 0 | int count = countContainers(polygons[i]->first, polygons, bboxes, |
438 | 0 | polygonCount); |
439 | 0 | if (count > max) { |
440 | 0 | parent = polygons[i]; |
441 | 0 | max = count; |
442 | 0 | } |
443 | 0 | } |
444 | 0 | } |
445 | |
|
446 | 0 | return parent; |
447 | 0 | } |
448 | | |
449 | | /** |
450 | | * Find the polygon to which a given hole should be allocated. Note that this |
451 | | * function will return null if no parent is found. |
452 | | * @param loop Inner loop describing a hole |
453 | | * @param polygon Head of a linked list of polygons to check |
454 | | * @param bboxes Bounding boxes for polygons, used in point-in-poly check |
455 | | * @param polygonCount Number of polygons to check |
456 | | * @return Pointer to parent polygon, or null if not found |
457 | | */ |
458 | | static const LinkedGeoPolygon *findPolygonForHole( |
459 | | const LinkedGeoLoop *loop, const LinkedGeoPolygon *polygon, |
460 | 0 | const BBox *bboxes, const int polygonCount) { |
461 | | // Early exit with no polygons |
462 | 0 | if (polygonCount == 0) { |
463 | 0 | return NULL; |
464 | 0 | } |
465 | | // Initialize arrays for candidate loops and their bounding boxes |
466 | 0 | const LinkedGeoPolygon **candidates = |
467 | 0 | H3_MEMORY(malloc)(polygonCount * sizeof(LinkedGeoPolygon *)); |
468 | 0 | assert(candidates != NULL); |
469 | 0 | const BBox **candidateBBoxes = |
470 | 0 | H3_MEMORY(malloc)(polygonCount * sizeof(BBox *)); |
471 | 0 | assert(candidateBBoxes != NULL); |
472 | | |
473 | | // Find all polygons that contain the loop |
474 | 0 | int candidateCount = 0; |
475 | 0 | int index = 0; |
476 | 0 | while (polygon) { |
477 | | // We are guaranteed not to overlap, so just test the first point |
478 | 0 | if (pointInsideLinkedGeoLoop(polygon->first, &bboxes[index], |
479 | 0 | &loop->first->vertex)) { |
480 | 0 | candidates[candidateCount] = polygon; |
481 | 0 | candidateBBoxes[candidateCount] = &bboxes[index]; |
482 | 0 | candidateCount++; |
483 | 0 | } |
484 | 0 | polygon = polygon->next; |
485 | 0 | index++; |
486 | 0 | } |
487 | | |
488 | | // The most deeply nested container is the immediate parent |
489 | 0 | const LinkedGeoPolygon *parent = |
490 | 0 | findDeepestContainer(candidates, candidateBBoxes, candidateCount); |
491 | | |
492 | | // Free allocated memory |
493 | 0 | H3_MEMORY(free)(candidates); |
494 | 0 | H3_MEMORY(free)(candidateBBoxes); |
495 | |
|
496 | 0 | return parent; |
497 | 0 | } |
498 | | |
499 | | /** |
500 | | * Normalize a LinkedGeoPolygon in-place into a structure following GeoJSON |
501 | | * MultiPolygon rules: Each polygon must have exactly one outer loop, which |
502 | | * must be first in the list, followed by any holes. Holes in this algorithm |
503 | | * are identified by winding order (holes are clockwise). |
504 | | * |
505 | | * Input to this function is assumed to be a single polygon including all |
506 | | * loops to normalize. It's assumed that a valid arrangement is possible. |
507 | | * |
508 | | * @param root Root polygon including all loops |
509 | | * @return 0 on success, or an error code > 0 for invalid input |
510 | | */ |
511 | 0 | H3Error normalizeMultiPolygon(LinkedGeoPolygon *root) { |
512 | | // We assume that the input is a single polygon with loops; |
513 | | // if it has multiple polygons, don't touch it |
514 | 0 | if (root->next) { |
515 | 0 | return E_FAILED; |
516 | 0 | } |
517 | | |
518 | | // Count loops, exiting early if there's only one |
519 | 0 | int loopCount = countLinkedLoops(root); |
520 | 0 | if (loopCount <= 1) { |
521 | 0 | return E_SUCCESS; |
522 | 0 | } |
523 | | |
524 | 0 | H3Error resultCode = E_SUCCESS; |
525 | 0 | LinkedGeoPolygon *polygon = NULL; |
526 | 0 | LinkedGeoLoop *next; |
527 | 0 | int innerCount = 0; |
528 | 0 | int outerCount = 0; |
529 | | |
530 | | // Create an array to hold all of the inner loops. Note that |
531 | | // this array will never be full, as there will always be fewer |
532 | | // inner loops than outer loops. |
533 | 0 | LinkedGeoLoop **innerLoops = |
534 | 0 | H3_MEMORY(malloc)(loopCount * sizeof(LinkedGeoLoop *)); |
535 | 0 | assert(innerLoops != NULL); |
536 | | |
537 | | // Create an array to hold the bounding boxes for the outer loops |
538 | 0 | BBox *bboxes = H3_MEMORY(malloc)(loopCount * sizeof(BBox)); |
539 | 0 | assert(bboxes != NULL); |
540 | | |
541 | | // Get the first loop and unlink it from root |
542 | 0 | LinkedGeoLoop *loop = root->first; |
543 | 0 | *root = (LinkedGeoPolygon){0}; |
544 | | |
545 | | // Iterate over all loops, moving inner loops into an array and |
546 | | // assigning outer loops to new polygons |
547 | 0 | while (loop) { |
548 | 0 | if (isClockwiseLinkedGeoLoop(loop)) { |
549 | 0 | innerLoops[innerCount] = loop; |
550 | 0 | innerCount++; |
551 | 0 | } else { |
552 | 0 | polygon = polygon == NULL ? root : addNewLinkedPolygon(polygon); |
553 | 0 | addLinkedLoop(polygon, loop); |
554 | 0 | bboxFromLinkedGeoLoop(loop, &bboxes[outerCount]); |
555 | 0 | outerCount++; |
556 | 0 | } |
557 | | // get the next loop and unlink it from this one |
558 | 0 | next = loop->next; |
559 | 0 | loop->next = NULL; |
560 | 0 | loop = next; |
561 | 0 | } |
562 | | |
563 | | // Find polygon for each inner loop and assign the hole to it |
564 | 0 | for (int i = 0; i < innerCount; i++) { |
565 | 0 | polygon = (LinkedGeoPolygon *)findPolygonForHole(innerLoops[i], root, |
566 | 0 | bboxes, outerCount); |
567 | 0 | if (polygon) { |
568 | 0 | addLinkedLoop(polygon, innerLoops[i]); |
569 | 0 | } else { |
570 | | // If we can't find a polygon (possible with invalid input), then |
571 | | // we need to release the memory for the hole, because the loop has |
572 | | // been unlinked from the root and the caller will no longer have |
573 | | // a way to destroy it with destroyLinkedMultiPolygon. |
574 | 0 | destroyLinkedGeoLoop(innerLoops[i]); |
575 | 0 | H3_MEMORY(free)(innerLoops[i]); |
576 | 0 | resultCode = E_FAILED; |
577 | 0 | } |
578 | 0 | } |
579 | | |
580 | | // Free allocated memory |
581 | 0 | H3_MEMORY(free)(innerLoops); |
582 | 0 | H3_MEMORY(free)(bboxes); |
583 | |
|
584 | 0 | return resultCode; |
585 | 0 | } |
586 | | |
587 | | // Define macros used in polygon algos for LinkedGeoLoop |
588 | | #define TYPE LinkedGeoLoop |
589 | 0 | #define INIT_ITERATION INIT_ITERATION_LINKED_LOOP |
590 | 0 | #define ITERATE ITERATE_LINKED_LOOP |
591 | 0 | #define IS_EMPTY IS_EMPTY_LINKED_LOOP |
592 | | |
593 | | #include "polygonAlgos.h" |
594 | | |
595 | | #undef TYPE |
596 | | #undef IS_EMPTY |
597 | | #undef INIT_ITERATION |
598 | | #undef ITERATE |