Coverage Report

Created: 2025-07-11 06:31

/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