/src/h3/src/h3lib/lib/linkedGeo.c
Line | Count | Source (jump to first uncovered line) |
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 "h3api.h" |
27 | | #include "latLng.h" |
28 | | |
29 | | /** |
30 | | * Add a linked polygon to the current polygon |
31 | | * @param polygon Polygon to add link to |
32 | | * @return Pointer to new polygon |
33 | | */ |
34 | 0 | LinkedGeoPolygon *addNewLinkedPolygon(LinkedGeoPolygon *polygon) { |
35 | 0 | assert(polygon->next == NULL); |
36 | 0 | LinkedGeoPolygon *next = H3_MEMORY(calloc)(1, sizeof(*next)); |
37 | 0 | assert(next != NULL); |
38 | 0 | polygon->next = next; |
39 | 0 | return next; |
40 | 0 | } |
41 | | |
42 | | /** |
43 | | * Add a new linked loop to the current polygon |
44 | | * @param polygon Polygon to add loop to |
45 | | * @return Pointer to loop |
46 | | */ |
47 | 0 | LinkedGeoLoop *addNewLinkedLoop(LinkedGeoPolygon *polygon) { |
48 | 0 | LinkedGeoLoop *loop = H3_MEMORY(calloc)(1, sizeof(*loop)); |
49 | 0 | assert(loop != NULL); |
50 | 0 | return addLinkedLoop(polygon, loop); |
51 | 0 | } |
52 | | |
53 | | /** |
54 | | * Add an existing linked loop to the current polygon |
55 | | * @param polygon Polygon to add loop to |
56 | | * @return Pointer to loop |
57 | | */ |
58 | 0 | LinkedGeoLoop *addLinkedLoop(LinkedGeoPolygon *polygon, LinkedGeoLoop *loop) { |
59 | 0 | LinkedGeoLoop *last = polygon->last; |
60 | 0 | if (last == NULL) { |
61 | 0 | assert(polygon->first == NULL); |
62 | 0 | polygon->first = loop; |
63 | 0 | } else { |
64 | 0 | last->next = loop; |
65 | 0 | } |
66 | 0 | polygon->last = loop; |
67 | 0 | return loop; |
68 | 0 | } |
69 | | |
70 | | /** |
71 | | * Add a new linked coordinate to the current loop |
72 | | * @param loop Loop to add coordinate to |
73 | | * @param vertex Coordinate to add |
74 | | * @return Pointer to the coordinate |
75 | | */ |
76 | 0 | LinkedLatLng *addLinkedCoord(LinkedGeoLoop *loop, const LatLng *vertex) { |
77 | 0 | LinkedLatLng *coord = H3_MEMORY(malloc)(sizeof(*coord)); |
78 | 0 | assert(coord != NULL); |
79 | 0 | *coord = (LinkedLatLng){.vertex = *vertex, .next = NULL}; |
80 | 0 | LinkedLatLng *last = loop->last; |
81 | 0 | if (last == NULL) { |
82 | 0 | assert(loop->first == NULL); |
83 | 0 | loop->first = coord; |
84 | 0 | } else { |
85 | 0 | last->next = coord; |
86 | 0 | } |
87 | 0 | loop->last = coord; |
88 | 0 | return coord; |
89 | 0 | } |
90 | | |
91 | | /** |
92 | | * Free all allocated memory for a linked geo loop. The caller is |
93 | | * responsible for freeing memory allocated to input loop struct. |
94 | | * @param loop Loop to free |
95 | | */ |
96 | 0 | void destroyLinkedGeoLoop(LinkedGeoLoop *loop) { |
97 | 0 | LinkedLatLng *nextCoord; |
98 | 0 | for (LinkedLatLng *currentCoord = loop->first; currentCoord != NULL; |
99 | 0 | currentCoord = nextCoord) { |
100 | 0 | nextCoord = currentCoord->next; |
101 | 0 | H3_MEMORY(free)(currentCoord); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | /** |
106 | | * Free all allocated memory for a linked geo structure. The caller is |
107 | | * responsible for freeing memory allocated to input polygon struct. |
108 | | * @param polygon Pointer to the first polygon in the structure |
109 | | */ |
110 | 0 | void H3_EXPORT(destroyLinkedMultiPolygon)(LinkedGeoPolygon *polygon) { |
111 | | // flag to skip the input polygon |
112 | 0 | bool skip = true; |
113 | 0 | LinkedGeoPolygon *nextPolygon; |
114 | 0 | LinkedGeoLoop *nextLoop; |
115 | 0 | for (LinkedGeoPolygon *currentPolygon = polygon; currentPolygon != NULL; |
116 | 0 | currentPolygon = nextPolygon) { |
117 | 0 | for (LinkedGeoLoop *currentLoop = currentPolygon->first; |
118 | 0 | currentLoop != NULL; currentLoop = nextLoop) { |
119 | 0 | destroyLinkedGeoLoop(currentLoop); |
120 | 0 | nextLoop = currentLoop->next; |
121 | 0 | H3_MEMORY(free)(currentLoop); |
122 | 0 | } |
123 | 0 | nextPolygon = currentPolygon->next; |
124 | 0 | if (skip) { |
125 | | // do not free the input polygon |
126 | 0 | skip = false; |
127 | 0 | } else { |
128 | 0 | H3_MEMORY(free)(currentPolygon); |
129 | 0 | } |
130 | 0 | } |
131 | 0 | } |
132 | | |
133 | | /** |
134 | | * Count the number of polygons in a linked list |
135 | | * @param polygon Starting polygon |
136 | | * @return Count |
137 | | */ |
138 | 0 | int countLinkedPolygons(LinkedGeoPolygon *polygon) { |
139 | 0 | int count = 0; |
140 | 0 | while (polygon != NULL) { |
141 | 0 | count++; |
142 | 0 | polygon = polygon->next; |
143 | 0 | } |
144 | 0 | return count; |
145 | 0 | } |
146 | | |
147 | | /** |
148 | | * Count the number of linked loops in a polygon |
149 | | * @param polygon Polygon to count loops for |
150 | | * @return Count |
151 | | */ |
152 | 0 | int countLinkedLoops(LinkedGeoPolygon *polygon) { |
153 | 0 | LinkedGeoLoop *loop = polygon->first; |
154 | 0 | int count = 0; |
155 | 0 | while (loop != NULL) { |
156 | 0 | count++; |
157 | 0 | loop = loop->next; |
158 | 0 | } |
159 | 0 | return count; |
160 | 0 | } |
161 | | |
162 | | /** |
163 | | * Count the number of coordinates in a loop |
164 | | * @param loop Loop to count coordinates for |
165 | | * @return Count |
166 | | */ |
167 | 0 | int countLinkedCoords(LinkedGeoLoop *loop) { |
168 | 0 | LinkedLatLng *coord = loop->first; |
169 | 0 | int count = 0; |
170 | 0 | while (coord != NULL) { |
171 | 0 | count++; |
172 | 0 | coord = coord->next; |
173 | 0 | } |
174 | 0 | return count; |
175 | 0 | } |
176 | | |
177 | | /** |
178 | | * Count the number of polygons containing a given loop. |
179 | | * @param loop Loop to count containers for |
180 | | * @param polygons Polygons to test |
181 | | * @param bboxes Bounding boxes for polygons, used in point-in-poly check |
182 | | * @param polygonCount Number of polygons in the test array |
183 | | * @return Number of polygons containing the loop |
184 | | */ |
185 | | static int countContainers(const LinkedGeoLoop *loop, |
186 | | const LinkedGeoPolygon **polygons, |
187 | 0 | const BBox **bboxes, const int polygonCount) { |
188 | 0 | int containerCount = 0; |
189 | 0 | for (int i = 0; i < polygonCount; i++) { |
190 | 0 | if (loop != polygons[i]->first && |
191 | 0 | pointInsideLinkedGeoLoop(polygons[i]->first, bboxes[i], |
192 | 0 | &loop->first->vertex)) { |
193 | 0 | containerCount++; |
194 | 0 | } |
195 | 0 | } |
196 | 0 | return containerCount; |
197 | 0 | } |
198 | | |
199 | | /** |
200 | | * Given a list of nested containers, find the one most deeply nested. |
201 | | * @param polygons Polygon containers to check |
202 | | * @param bboxes Bounding boxes for polygons, used in point-in-poly check |
203 | | * @param polygonCount Number of polygons in the list |
204 | | * @return Deepest container, or null if list is empty |
205 | | */ |
206 | | static const LinkedGeoPolygon *findDeepestContainer( |
207 | | const LinkedGeoPolygon **polygons, const BBox **bboxes, |
208 | 0 | const int polygonCount) { |
209 | | // Set the initial return value to the first candidate |
210 | 0 | const LinkedGeoPolygon *parent = polygonCount > 0 ? polygons[0] : NULL; |
211 | | |
212 | | // If we have multiple polygons, they must be nested inside each other. |
213 | | // Find the innermost polygon by taking the one with the most containers |
214 | | // in the list. |
215 | 0 | if (polygonCount > 1) { |
216 | 0 | int max = -1; |
217 | 0 | for (int i = 0; i < polygonCount; i++) { |
218 | 0 | int count = countContainers(polygons[i]->first, polygons, bboxes, |
219 | 0 | polygonCount); |
220 | 0 | if (count > max) { |
221 | 0 | parent = polygons[i]; |
222 | 0 | max = count; |
223 | 0 | } |
224 | 0 | } |
225 | 0 | } |
226 | |
|
227 | 0 | return parent; |
228 | 0 | } |
229 | | |
230 | | /** |
231 | | * Find the polygon to which a given hole should be allocated. Note that this |
232 | | * function will return null if no parent is found. |
233 | | * @param loop Inner loop describing a hole |
234 | | * @param polygon Head of a linked list of polygons to check |
235 | | * @param bboxes Bounding boxes for polygons, used in point-in-poly check |
236 | | * @param polygonCount Number of polygons to check |
237 | | * @return Pointer to parent polygon, or null if not found |
238 | | */ |
239 | | static const LinkedGeoPolygon *findPolygonForHole( |
240 | | const LinkedGeoLoop *loop, const LinkedGeoPolygon *polygon, |
241 | 0 | const BBox *bboxes, const int polygonCount) { |
242 | | // Early exit with no polygons |
243 | 0 | if (polygonCount == 0) { |
244 | 0 | return NULL; |
245 | 0 | } |
246 | | // Initialize arrays for candidate loops and their bounding boxes |
247 | 0 | const LinkedGeoPolygon **candidates = |
248 | 0 | H3_MEMORY(malloc)(polygonCount * sizeof(LinkedGeoPolygon *)); |
249 | 0 | assert(candidates != NULL); |
250 | 0 | const BBox **candidateBBoxes = |
251 | 0 | H3_MEMORY(malloc)(polygonCount * sizeof(BBox *)); |
252 | 0 | assert(candidateBBoxes != NULL); |
253 | | |
254 | | // Find all polygons that contain the loop |
255 | 0 | int candidateCount = 0; |
256 | 0 | int index = 0; |
257 | 0 | while (polygon) { |
258 | | // We are guaranteed not to overlap, so just test the first point |
259 | 0 | if (pointInsideLinkedGeoLoop(polygon->first, &bboxes[index], |
260 | 0 | &loop->first->vertex)) { |
261 | 0 | candidates[candidateCount] = polygon; |
262 | 0 | candidateBBoxes[candidateCount] = &bboxes[index]; |
263 | 0 | candidateCount++; |
264 | 0 | } |
265 | 0 | polygon = polygon->next; |
266 | 0 | index++; |
267 | 0 | } |
268 | | |
269 | | // The most deeply nested container is the immediate parent |
270 | 0 | const LinkedGeoPolygon *parent = |
271 | 0 | findDeepestContainer(candidates, candidateBBoxes, candidateCount); |
272 | | |
273 | | // Free allocated memory |
274 | 0 | H3_MEMORY(free)(candidates); |
275 | 0 | H3_MEMORY(free)(candidateBBoxes); |
276 | |
|
277 | 0 | return parent; |
278 | 0 | } |
279 | | |
280 | | /** |
281 | | * Normalize a LinkedGeoPolygon in-place into a structure following GeoJSON |
282 | | * MultiPolygon rules: Each polygon must have exactly one outer loop, which |
283 | | * must be first in the list, followed by any holes. Holes in this algorithm |
284 | | * are identified by winding order (holes are clockwise), which is guaranteed |
285 | | * by the h3SetToVertexGraph algorithm. |
286 | | * |
287 | | * Input to this function is assumed to be a single polygon including all |
288 | | * loops to normalize. It's assumed that a valid arrangement is possible. |
289 | | * |
290 | | * @param root Root polygon including all loops |
291 | | * @return 0 on success, or an error code > 0 for invalid input |
292 | | */ |
293 | 0 | H3Error normalizeMultiPolygon(LinkedGeoPolygon *root) { |
294 | | // We assume that the input is a single polygon with loops; |
295 | | // if it has multiple polygons, don't touch it |
296 | 0 | if (root->next) { |
297 | 0 | return E_FAILED; |
298 | 0 | } |
299 | | |
300 | | // Count loops, exiting early if there's only one |
301 | 0 | int loopCount = countLinkedLoops(root); |
302 | 0 | if (loopCount <= 1) { |
303 | 0 | return E_SUCCESS; |
304 | 0 | } |
305 | | |
306 | 0 | H3Error resultCode = E_SUCCESS; |
307 | 0 | LinkedGeoPolygon *polygon = NULL; |
308 | 0 | LinkedGeoLoop *next; |
309 | 0 | int innerCount = 0; |
310 | 0 | int outerCount = 0; |
311 | | |
312 | | // Create an array to hold all of the inner loops. Note that |
313 | | // this array will never be full, as there will always be fewer |
314 | | // inner loops than outer loops. |
315 | 0 | LinkedGeoLoop **innerLoops = |
316 | 0 | H3_MEMORY(malloc)(loopCount * sizeof(LinkedGeoLoop *)); |
317 | 0 | assert(innerLoops != NULL); |
318 | | |
319 | | // Create an array to hold the bounding boxes for the outer loops |
320 | 0 | BBox *bboxes = H3_MEMORY(malloc)(loopCount * sizeof(BBox)); |
321 | 0 | assert(bboxes != NULL); |
322 | | |
323 | | // Get the first loop and unlink it from root |
324 | 0 | LinkedGeoLoop *loop = root->first; |
325 | 0 | *root = (LinkedGeoPolygon){0}; |
326 | | |
327 | | // Iterate over all loops, moving inner loops into an array and |
328 | | // assigning outer loops to new polygons |
329 | 0 | while (loop) { |
330 | 0 | if (isClockwiseLinkedGeoLoop(loop)) { |
331 | 0 | innerLoops[innerCount] = loop; |
332 | 0 | innerCount++; |
333 | 0 | } else { |
334 | 0 | polygon = polygon == NULL ? root : addNewLinkedPolygon(polygon); |
335 | 0 | addLinkedLoop(polygon, loop); |
336 | 0 | bboxFromLinkedGeoLoop(loop, &bboxes[outerCount]); |
337 | 0 | outerCount++; |
338 | 0 | } |
339 | | // get the next loop and unlink it from this one |
340 | 0 | next = loop->next; |
341 | 0 | loop->next = NULL; |
342 | 0 | loop = next; |
343 | 0 | } |
344 | | |
345 | | // Find polygon for each inner loop and assign the hole to it |
346 | 0 | for (int i = 0; i < innerCount; i++) { |
347 | 0 | polygon = (LinkedGeoPolygon *)findPolygonForHole(innerLoops[i], root, |
348 | 0 | bboxes, outerCount); |
349 | 0 | if (polygon) { |
350 | 0 | addLinkedLoop(polygon, innerLoops[i]); |
351 | 0 | } else { |
352 | | // If we can't find a polygon (possible with invalid input), then |
353 | | // we need to release the memory for the hole, because the loop has |
354 | | // been unlinked from the root and the caller will no longer have |
355 | | // a way to destroy it with destroyLinkedMultiPolygon. |
356 | 0 | destroyLinkedGeoLoop(innerLoops[i]); |
357 | 0 | H3_MEMORY(free)(innerLoops[i]); |
358 | 0 | resultCode = E_FAILED; |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | | // Free allocated memory |
363 | 0 | H3_MEMORY(free)(innerLoops); |
364 | 0 | H3_MEMORY(free)(bboxes); |
365 | |
|
366 | 0 | return resultCode; |
367 | 0 | } |
368 | | |
369 | | // Define macros used in polygon algos for LinkedGeoLoop |
370 | | #define TYPE LinkedGeoLoop |
371 | 0 | #define INIT_ITERATION INIT_ITERATION_LINKED_LOOP |
372 | 0 | #define ITERATE ITERATE_LINKED_LOOP |
373 | 0 | #define IS_EMPTY IS_EMPTY_LINKED_LOOP |
374 | | |
375 | | #include "polygonAlgos.h" |
376 | | |
377 | | #undef TYPE |
378 | | #undef IS_EMPTY |
379 | | #undef INIT_ITERATION |
380 | | #undef ITERATE |