Coverage Report

Created: 2025-07-12 06:04

/src/h3/src/h3lib/lib/polygon.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2018-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 polygon.c
17
 * @brief Polygon (GeoLoop) algorithms
18
 */
19
20
#include "polygon.h"
21
22
#include <float.h>
23
#include <math.h>
24
#include <stdbool.h>
25
26
#include "bbox.h"
27
#include "constants.h"
28
#include "h3api.h"
29
#include "latLng.h"
30
#include "linkedGeo.h"
31
32
// Define macros used in polygon algos for GeoLoop
33
#define TYPE GeoLoop
34
0
#define INIT_ITERATION INIT_ITERATION_GEOFENCE
35
0
#define ITERATE ITERATE_GEOFENCE
36
0
#define IS_EMPTY IS_EMPTY_GEOFENCE
37
38
#include "polygonAlgos.h"
39
40
#undef TYPE
41
#undef INIT_ITERATION
42
#undef ITERATE
43
#undef IS_EMPTY
44
45
/**
46
 * Whether the flags for the polyfill operation are valid
47
 * TODO: Move to polyfill.c when the old algo is removed
48
 * @param  flags Flags to validate
49
 * @return       Whether the flags are valid
50
 */
51
0
H3Error validatePolygonFlags(uint32_t flags) {
52
0
    if (flags & (~FLAG_CONTAINMENT_MODE_MASK) ||
53
0
        FLAG_GET_CONTAINMENT_MODE(flags) >= CONTAINMENT_INVALID) {
54
0
        return E_OPTION_INVALID;
55
0
    }
56
0
    return E_SUCCESS;
57
0
}
58
59
/**
60
 * Create a bounding box from a GeoPolygon
61
 * @param polygon Input GeoPolygon
62
 * @param bboxes  Output bboxes, one for the outer loop and one for each hole
63
 */
64
0
void bboxesFromGeoPolygon(const GeoPolygon *polygon, BBox *bboxes) {
65
0
    bboxFromGeoLoop(&polygon->geoloop, &bboxes[0]);
66
0
    for (int i = 0; i < polygon->numHoles; i++) {
67
0
        bboxFromGeoLoop(&polygon->holes[i], &bboxes[i + 1]);
68
0
    }
69
0
}
70
71
/**
72
 * pointInsidePolygon takes a given GeoPolygon data structure and
73
 * checks if it contains a given geo coordinate.
74
 *
75
 * @param geoPolygon The geoloop and holes defining the relevant area
76
 * @param bboxes     The bboxes for the main geoloop and each of its holes
77
 * @param coord      The coordinate to check
78
 * @return           Whether the point is contained
79
 */
80
bool pointInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes,
81
0
                        const LatLng *coord) {
82
    // Start with contains state of primary geoloop
83
0
    bool contains =
84
0
        pointInsideGeoLoop(&(geoPolygon->geoloop), &bboxes[0], coord);
85
86
    // If the point is contained in the primary geoloop, but there are holes in
87
    // the geoloop iterate through all holes and return false if the point is
88
    // contained in any hole
89
0
    if (contains && geoPolygon->numHoles > 0) {
90
0
        for (int i = 0; i < geoPolygon->numHoles; i++) {
91
0
            if (pointInsideGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1],
92
0
                                   coord)) {
93
0
                return false;
94
0
            }
95
0
        }
96
0
    }
97
98
0
    return contains;
99
0
}
100
101
/**
102
 * Whether a cell boundary is completely contained by a polygon.
103
 * @param  geoPolygon The polygon to test
104
 * @param  bboxes     The bboxes for the main geoloop and each of its holes
105
 * @param  boundary   The cell boundary to test
106
 * @return            Whether the cell boundary is contained
107
 */
108
bool cellBoundaryInsidePolygon(const GeoPolygon *geoPolygon, const BBox *bboxes,
109
                               const CellBoundary *boundary,
110
0
                               const BBox *boundaryBBox) {
111
    // First test a single point. Note that this fails fast if point is outside
112
    // bounding box.
113
0
    if (!pointInsidePolygon(geoPolygon, &bboxes[0], &boundary->verts[0])) {
114
0
        return false;
115
0
    }
116
117
    // If a point is contained, check for any line intersections
118
0
    if (cellBoundaryCrossesGeoLoop(&(geoPolygon->geoloop), &bboxes[0], boundary,
119
0
                                   boundaryBBox)) {
120
0
        return false;
121
0
    }
122
123
    // Convert boundary to geoloop for point-inside check
124
0
    const GeoLoop boundaryLoop = {.numVerts = boundary->numVerts,
125
                                  // Without this cast, the compiler complains
126
                                  // that using const LatLng[] here discards
127
                                  // qualifiers. But this should be safe in
128
                                  // context, all downstream usage expects const
129
0
                                  .verts = (LatLng *)boundary->verts};
130
131
    // Check for line intersections with, or containment of, any hole
132
0
    for (int i = 0; i < geoPolygon->numHoles; i++) {
133
        // If the hole has no verts, it is not possible to intersect with it.
134
0
        if (geoPolygon->holes[i].numVerts > 0 &&
135
0
            (pointInsideGeoLoop(&boundaryLoop, boundaryBBox,
136
0
                                &geoPolygon->holes[i].verts[0]) ||
137
0
             cellBoundaryCrossesGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1],
138
0
                                        boundary, boundaryBBox))) {
139
0
            return false;
140
0
        }
141
0
    }
142
0
    return true;
143
0
}
144
145
/**
146
 * Whether any part of a cell boundary crosses a polygon. Crossing in this case
147
 * means whether any line segments intersect; it does not include containment.
148
 * @param  geoPolygon The polygon to test
149
 * @param  bboxes     The bboxes for the main geoloop and each of its holes
150
 * @param  boundary   The cell boundary to test
151
 * @return            Whether the cell boundary is contained
152
 */
153
bool cellBoundaryCrossesPolygon(const GeoPolygon *geoPolygon,
154
                                const BBox *bboxes,
155
                                const CellBoundary *boundary,
156
0
                                const BBox *boundaryBBox) {
157
    // Check for line intersections with outer loop
158
0
    if (cellBoundaryCrossesGeoLoop(&(geoPolygon->geoloop), &bboxes[0], boundary,
159
0
                                   boundaryBBox)) {
160
0
        return true;
161
0
    }
162
    // Check for line intersections with any hole
163
0
    for (int i = 0; i < geoPolygon->numHoles; i++) {
164
0
        if (cellBoundaryCrossesGeoLoop(&(geoPolygon->holes[i]), &bboxes[i + 1],
165
0
                                       boundary, boundaryBBox)) {
166
0
            return true;
167
0
        }
168
0
    }
169
0
    return false;
170
0
}
171
172
/**
173
 * Whether a cell boundary crosses a geo loop. Crossing in this case means
174
 * whether any line segments intersect; it does not include containment.
175
 * @param  geoloop  Geo loop to test
176
 * @param  boundary Cell boundary to test
177
 * @return          Whether any line segments in the boundary intersect any line
178
 * segments in the geo loop
179
 */
180
bool cellBoundaryCrossesGeoLoop(const GeoLoop *geoloop, const BBox *loopBBox,
181
                                const CellBoundary *boundary,
182
0
                                const BBox *boundaryBBox) {
183
0
    if (!bboxOverlapsBBox(loopBBox, boundaryBBox)) {
184
0
        return false;
185
0
    }
186
0
    LongitudeNormalization loopNormalization;
187
0
    LongitudeNormalization boundaryNormalization;
188
0
    bboxNormalization(loopBBox, boundaryBBox, &loopNormalization,
189
0
                      &boundaryNormalization);
190
191
0
    CellBoundary normalBoundary = *boundary;
192
0
    for (int i = 0; i < boundary->numVerts; i++) {
193
0
        normalBoundary.verts[i].lng =
194
0
            normalizeLng(normalBoundary.verts[i].lng, boundaryNormalization);
195
0
    }
196
197
0
    BBox normalBoundaryBBox = {
198
0
        .north = boundaryBBox->north,
199
0
        .south = boundaryBBox->south,
200
0
        .east = normalizeLng(boundaryBBox->east, boundaryNormalization),
201
0
        .west = normalizeLng(boundaryBBox->west, boundaryNormalization)};
202
203
0
    LatLng loop1;
204
0
    LatLng loop2;
205
0
    for (int i = 0; i < geoloop->numVerts; i++) {
206
0
        loop1 = geoloop->verts[i];
207
0
        loop1.lng = normalizeLng(loop1.lng, loopNormalization);
208
0
        loop2 = geoloop->verts[(i + 1) % geoloop->numVerts];
209
0
        loop2.lng = normalizeLng(loop2.lng, loopNormalization);
210
211
        // Quick check if the line segment overlaps our bbox
212
0
        if ((loop1.lat >= normalBoundaryBBox.north &&
213
0
             loop2.lat >= normalBoundaryBBox.north) ||
214
0
            (loop1.lat <= normalBoundaryBBox.south &&
215
0
             loop2.lat <= normalBoundaryBBox.south) ||
216
0
            (loop1.lng <= normalBoundaryBBox.west &&
217
0
             loop2.lng <= normalBoundaryBBox.west) ||
218
0
            (loop1.lng >= normalBoundaryBBox.east &&
219
0
             loop2.lng >= normalBoundaryBBox.east)) {
220
0
            continue;
221
0
        }
222
223
0
        for (int j = 0; j < normalBoundary.numVerts; j++) {
224
0
            if (lineCrossesLine(
225
0
                    &loop1, &loop2, &normalBoundary.verts[j],
226
0
                    &normalBoundary.verts[(j + 1) % normalBoundary.numVerts])) {
227
0
                return true;
228
0
            }
229
0
        }
230
0
    }
231
0
    return false;
232
0
}
233
234
/**
235
 * Whether two lines intersect. This is a purely Cartesian implementation
236
 * and does not consider anti-meridian wrapping, poles, etc. Based on
237
 * http://www.jeffreythompson.org/collision-detection/line-line.php
238
 * @param  a1 Start of line A
239
 * @param  a2 End of line A
240
 * @param  b1 Start of line B
241
 * @param  b2 End of line B
242
 * @return    Whether the lines intersect
243
 */
244
bool lineCrossesLine(const LatLng *a1, const LatLng *a2, const LatLng *b1,
245
0
                     const LatLng *b2) {
246
0
    double denom = ((b2->lng - b1->lng) * (a2->lat - a1->lat) -
247
0
                    (b2->lat - b1->lat) * (a2->lng - a1->lng));
248
0
    if (!denom) return false;
249
250
0
    double test;
251
0
    test = ((b2->lat - b1->lat) * (a1->lng - b1->lng) -
252
0
            (b2->lng - b1->lng) * (a1->lat - b1->lat)) /
253
0
           denom;
254
0
    if (test < 0 || test > 1) return false;
255
256
0
    test = ((a2->lat - a1->lat) * (a1->lng - b1->lng) -
257
0
            (a2->lng - a1->lng) * (a1->lat - b1->lat)) /
258
0
           denom;
259
0
    return (test >= 0 && test <= 1);
260
0
}