/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 | } |