/src/gdal/ogr/ograssemblepolygon.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: S-57 Reader |
4 | | * Purpose: Implements polygon assembly from a bunch of arcs. |
5 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 1999, Frank Warmerdam |
9 | | * Copyright (c) 2009-2011, Even Rouault <even dot rouault at spatialys.com> |
10 | | * |
11 | | * SPDX-License-Identifier: MIT |
12 | | ****************************************************************************/ |
13 | | |
14 | | #include "cpl_port.h" |
15 | | #include "ogr_api.h" |
16 | | |
17 | | #include <cmath> |
18 | | #include <cstddef> |
19 | | #include <list> |
20 | | #include <vector> |
21 | | |
22 | | #include "ogr_core.h" |
23 | | #include "ogr_geometry.h" |
24 | | #include "cpl_conv.h" |
25 | | #include "cpl_error.h" |
26 | | |
27 | | /************************************************************************/ |
28 | | /* CheckPoints() */ |
29 | | /* */ |
30 | | /* Check if two points are closer than the current best */ |
31 | | /* distance. Update the current best distance if they are. */ |
32 | | /************************************************************************/ |
33 | | |
34 | | static bool CheckPoints(OGRLineString *poLine1, int iPoint1, |
35 | | OGRLineString *poLine2, int iPoint2, |
36 | | double *pdfDistance) |
37 | | |
38 | 5.92M | { |
39 | 5.92M | if (pdfDistance == nullptr || *pdfDistance == 0) |
40 | 1.09M | { |
41 | 1.09M | if (poLine1->getX(iPoint1) == poLine2->getX(iPoint2) && |
42 | 1.09M | poLine1->getY(iPoint1) == poLine2->getY(iPoint2)) |
43 | 174k | { |
44 | 174k | if (pdfDistance) |
45 | 34.7k | *pdfDistance = 0.0; |
46 | 174k | return true; |
47 | 174k | } |
48 | 919k | return false; |
49 | 1.09M | } |
50 | | |
51 | 4.83M | const double dfDeltaX = |
52 | 4.83M | std::abs(poLine1->getX(iPoint1) - poLine2->getX(iPoint2)); |
53 | | |
54 | 4.83M | if (dfDeltaX > *pdfDistance) |
55 | 1.88M | return false; |
56 | | |
57 | 2.94M | const double dfDeltaY = |
58 | 2.94M | std::abs(poLine1->getY(iPoint1) - poLine2->getY(iPoint2)); |
59 | | |
60 | 2.94M | if (dfDeltaY > *pdfDistance) |
61 | 629k | return false; |
62 | | |
63 | 2.31M | const double dfDistance = sqrt(dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY); |
64 | | |
65 | 2.31M | if (dfDistance < *pdfDistance) |
66 | 392k | { |
67 | 392k | *pdfDistance = dfDistance; |
68 | 392k | return true; |
69 | 392k | } |
70 | | |
71 | 1.92M | return false; |
72 | 2.31M | } |
73 | | |
74 | | /************************************************************************/ |
75 | | /* AddEdgeToRing() */ |
76 | | /************************************************************************/ |
77 | | |
78 | | static void AddEdgeToRing(OGRLinearRing *poRing, OGRLineString *poLine, |
79 | | bool bReverse, double dfTolerance) |
80 | | |
81 | 205k | { |
82 | | /* -------------------------------------------------------------------- */ |
83 | | /* Establish order and range of traverse. */ |
84 | | /* -------------------------------------------------------------------- */ |
85 | 205k | const int nVertToAdd = poLine->getNumPoints(); |
86 | | |
87 | 205k | int iStart = bReverse ? nVertToAdd - 1 : 0; |
88 | 205k | const int iEnd = bReverse ? 0 : nVertToAdd - 1; |
89 | 205k | const int iStep = bReverse ? -1 : 1; |
90 | | |
91 | | /* -------------------------------------------------------------------- */ |
92 | | /* Should we skip a repeating vertex? */ |
93 | | /* -------------------------------------------------------------------- */ |
94 | 205k | if (poRing->getNumPoints() > 0 && |
95 | 205k | CheckPoints(poRing, poRing->getNumPoints() - 1, poLine, iStart, |
96 | 91.7k | &dfTolerance)) |
97 | 91.7k | { |
98 | 91.7k | iStart += iStep; |
99 | 91.7k | } |
100 | | |
101 | 205k | poRing->addSubLineString(poLine, iStart, iEnd); |
102 | 205k | } |
103 | | |
104 | | /************************************************************************/ |
105 | | /* OGRBuildPolygonFromEdges() */ |
106 | | /************************************************************************/ |
107 | | |
108 | | /** |
109 | | * Build a ring from a bunch of arcs. |
110 | | * |
111 | | * @param hLines handle to an OGRGeometryCollection (or OGRMultiLineString) |
112 | | * containing the line string geometries to be built into rings. |
113 | | * @param bBestEffort not yet implemented???. |
114 | | * @param bAutoClose indicates if the ring should be close when first and |
115 | | * last points of the ring are the same. |
116 | | * @param dfTolerance tolerance into which two arcs are considered |
117 | | * close enough to be joined. |
118 | | * @param peErr OGRERR_NONE on success, or OGRERR_FAILURE on failure. |
119 | | * @return a handle to the new geometry, a polygon. |
120 | | * |
121 | | */ |
122 | | |
123 | | OGRGeometryH OGRBuildPolygonFromEdges(OGRGeometryH hLines, |
124 | | CPL_UNUSED int bBestEffort, |
125 | | int bAutoClose, double dfTolerance, |
126 | | OGRErr *peErr) |
127 | | |
128 | 151k | { |
129 | 151k | if (hLines == nullptr) |
130 | 0 | { |
131 | 0 | if (peErr != nullptr) |
132 | 0 | *peErr = OGRERR_NONE; |
133 | 0 | return nullptr; |
134 | 0 | } |
135 | | |
136 | | /* -------------------------------------------------------------------- */ |
137 | | /* Check for the case of a geometrycollection that can be */ |
138 | | /* promoted to MultiLineString. */ |
139 | | /* -------------------------------------------------------------------- */ |
140 | 151k | OGRGeometry *poGeom = OGRGeometry::FromHandle(hLines); |
141 | 151k | if (wkbFlatten(poGeom->getGeometryType()) == wkbGeometryCollection) |
142 | 108k | { |
143 | 108k | for (auto &&poMember : poGeom->toGeometryCollection()) |
144 | 82.0k | { |
145 | 82.0k | if (wkbFlatten(poMember->getGeometryType()) != wkbLineString) |
146 | 6.59k | { |
147 | 6.59k | if (peErr != nullptr) |
148 | 6.59k | *peErr = OGRERR_FAILURE; |
149 | 6.59k | CPLError(CE_Failure, CPLE_NotSupported, |
150 | 6.59k | "The geometry collection contains " |
151 | 6.59k | "non-line string geometries"); |
152 | 6.59k | return nullptr; |
153 | 6.59k | } |
154 | 82.0k | } |
155 | 108k | } |
156 | 42.9k | else if (wkbFlatten(poGeom->getGeometryType()) != wkbMultiLineString) |
157 | 0 | { |
158 | 0 | if (peErr != nullptr) |
159 | 0 | *peErr = OGRERR_FAILURE; |
160 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
161 | 0 | "The passed geometry is not an OGRGeometryCollection " |
162 | 0 | "(or OGRMultiLineString) " |
163 | 0 | "containing line string geometries"); |
164 | 0 | return nullptr; |
165 | 0 | } |
166 | | |
167 | 144k | bool bSuccess = true; |
168 | 144k | OGRGeometryCollection *poLines = poGeom->toGeometryCollection(); |
169 | 144k | std::vector<OGRLinearRing *> apoRings; |
170 | | |
171 | | /* -------------------------------------------------------------------- */ |
172 | | /* Setup array of line markers indicating if they have been */ |
173 | | /* added to a ring yet. */ |
174 | | /* -------------------------------------------------------------------- */ |
175 | 144k | const int nEdges = poLines->getNumGeometries(); |
176 | 144k | std::list<OGRLineString *> oListEdges; |
177 | 358k | for (int i = 0; i < nEdges; i++) |
178 | 214k | { |
179 | 214k | OGRLineString *poLine = poLines->getGeometryRef(i)->toLineString(); |
180 | 214k | if (poLine->getNumPoints() >= 2) |
181 | 205k | { |
182 | 205k | oListEdges.push_back(poLine); |
183 | 205k | } |
184 | 214k | } |
185 | | |
186 | | /* ==================================================================== */ |
187 | | /* Loop generating rings. */ |
188 | | /* ==================================================================== */ |
189 | 258k | while (!oListEdges.empty()) |
190 | 113k | { |
191 | | /* -------------------------------------------------------------------- |
192 | | */ |
193 | | /* Find the first unconsumed edge. */ |
194 | | /* -------------------------------------------------------------------- |
195 | | */ |
196 | 113k | OGRLineString *poLine = oListEdges.front(); |
197 | 113k | oListEdges.erase(oListEdges.begin()); |
198 | | |
199 | | /* -------------------------------------------------------------------- |
200 | | */ |
201 | | /* Start a new ring, copying in the current line directly */ |
202 | | /* -------------------------------------------------------------------- |
203 | | */ |
204 | 113k | OGRLinearRing *poRing = new OGRLinearRing(); |
205 | | |
206 | 113k | AddEdgeToRing(poRing, poLine, FALSE, 0); |
207 | | |
208 | | /* ==================================================================== |
209 | | */ |
210 | | /* Loop adding edges to this ring until we make a whole pass */ |
211 | | /* within finding anything to add. */ |
212 | | /* ==================================================================== |
213 | | */ |
214 | 113k | bool bWorkDone = true; |
215 | 113k | double dfBestDist = dfTolerance; |
216 | | |
217 | 221k | while (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1, |
218 | 221k | nullptr) && |
219 | 221k | !oListEdges.empty() && bWorkDone) |
220 | 107k | { |
221 | 107k | bool bReverse = false; |
222 | | |
223 | 107k | bWorkDone = false; |
224 | 107k | dfBestDist = dfTolerance; |
225 | | |
226 | | // We consider linking the end to the beginning. If this is |
227 | | // closer than any other option we will just close the loop. |
228 | | |
229 | | // CheckPoints(poRing, 0, poRing, poRing->getNumPoints()-1, |
230 | | // &dfBestDist); |
231 | | |
232 | | // Find unused edge with end point closest to our loose end. |
233 | 107k | OGRLineString *poBestEdge = nullptr; |
234 | 107k | std::list<OGRLineString *>::iterator oBestIter; |
235 | 2.68M | for (auto oIter = oListEdges.begin(); oIter != oListEdges.end(); |
236 | 2.57M | ++oIter) |
237 | 2.66M | { |
238 | 2.66M | poLine = *oIter; |
239 | | |
240 | 2.66M | if (CheckPoints(poLine, 0, poRing, poRing->getNumPoints() - 1, |
241 | 2.66M | &dfBestDist)) |
242 | 97.7k | { |
243 | 97.7k | poBestEdge = poLine; |
244 | 97.7k | oBestIter = oIter; |
245 | 97.7k | bReverse = false; |
246 | 97.7k | } |
247 | 2.66M | if (CheckPoints(poLine, poLine->getNumPoints() - 1, poRing, |
248 | 2.66M | poRing->getNumPoints() - 1, &dfBestDist)) |
249 | 83.3k | { |
250 | 83.3k | poBestEdge = poLine; |
251 | 83.3k | oBestIter = oIter; |
252 | 83.3k | bReverse = true; |
253 | 83.3k | } |
254 | | |
255 | | // If we found an exact match, jump now. |
256 | 2.66M | if (dfBestDist == 0.0 && poBestEdge != nullptr) |
257 | 87.6k | break; |
258 | 2.66M | } |
259 | | |
260 | | // We found one within tolerance - add it. |
261 | 107k | if (poBestEdge) |
262 | 91.7k | { |
263 | 91.7k | AddEdgeToRing(poRing, poBestEdge, bReverse, dfTolerance); |
264 | | |
265 | 91.7k | oListEdges.erase(oBestIter); |
266 | 91.7k | bWorkDone = true; |
267 | 91.7k | } |
268 | 107k | } |
269 | | |
270 | | /* -------------------------------------------------------------------- |
271 | | */ |
272 | | /* Did we fail to complete the ring? */ |
273 | | /* -------------------------------------------------------------------- |
274 | | */ |
275 | 113k | dfBestDist = dfTolerance; |
276 | | |
277 | 113k | if (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1, |
278 | 113k | &dfBestDist)) |
279 | 35.8k | { |
280 | 35.8k | CPLDebug("OGR", |
281 | 35.8k | "Failed to close ring %d.\n" |
282 | 35.8k | "End Points are: (%.8f,%.7f) and (%.7f,%.7f)", |
283 | 35.8k | static_cast<int>(apoRings.size()), poRing->getX(0), |
284 | 35.8k | poRing->getY(0), poRing->getX(poRing->getNumPoints() - 1), |
285 | 35.8k | poRing->getY(poRing->getNumPoints() - 1)); |
286 | | |
287 | 35.8k | bSuccess = false; |
288 | 35.8k | } |
289 | | |
290 | | /* -------------------------------------------------------------------- |
291 | | */ |
292 | | /* Do we need to auto-close this ring? */ |
293 | | /* -------------------------------------------------------------------- |
294 | | */ |
295 | 113k | dfBestDist = dfTolerance; |
296 | | |
297 | 113k | if (bAutoClose) |
298 | 102k | { |
299 | 102k | if (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1, |
300 | 102k | &dfBestDist)) |
301 | 26.1k | { |
302 | 26.1k | poRing->addPoint(poRing->getX(0), poRing->getY(0), |
303 | 26.1k | poRing->getZ(0)); |
304 | 26.1k | } |
305 | 75.8k | else if (!CheckPoints(poRing, 0, poRing, poRing->getNumPoints() - 1, |
306 | 75.8k | nullptr)) |
307 | 7.25k | { |
308 | | // The endpoints are very close but do not exactly match. |
309 | | // Alter the last one so it is equal to the first, to prevent |
310 | | // invalid self-intersecting rings. |
311 | 7.25k | poRing->setPoint(poRing->getNumPoints() - 1, poRing->getX(0), |
312 | 7.25k | poRing->getY(0), poRing->getZ(0)); |
313 | 7.25k | } |
314 | 102k | } |
315 | | |
316 | 113k | apoRings.push_back(poRing); |
317 | 113k | } // Next ring. |
318 | | |
319 | | /* -------------------------------------------------------------------- */ |
320 | | /* Identify exterior ring - it will be the largest. #3610 */ |
321 | | /* -------------------------------------------------------------------- */ |
322 | 144k | double maxarea = 0.0; |
323 | 144k | int maxring = -1; |
324 | 144k | OGREnvelope tenv; |
325 | | |
326 | 258k | for (int rn = 0; rn < static_cast<int>(apoRings.size()); ++rn) |
327 | 113k | { |
328 | 113k | apoRings[rn]->getEnvelope(&tenv); |
329 | 113k | const double tarea = (tenv.MaxX - tenv.MinX) * (tenv.MaxY - tenv.MinY); |
330 | 113k | if (tarea > maxarea) |
331 | 27.6k | { |
332 | 27.6k | maxarea = tarea; |
333 | 27.6k | maxring = rn; |
334 | 27.6k | } |
335 | 113k | } |
336 | | |
337 | 144k | OGRPolygon *poPolygon = new OGRPolygon(); |
338 | | |
339 | 144k | if (maxring != -1) |
340 | 23.7k | { |
341 | 23.7k | poPolygon->addRingDirectly(apoRings[maxring]); |
342 | 107k | for (int rn = 0; rn < static_cast<int>(apoRings.size()); ++rn) |
343 | 84.1k | { |
344 | 84.1k | if (rn == maxring) |
345 | 23.7k | continue; |
346 | 60.4k | poPolygon->addRingDirectly(apoRings[rn]); |
347 | 60.4k | } |
348 | 23.7k | } |
349 | 120k | else |
350 | 120k | { |
351 | 120k | for (auto &poRing : apoRings) |
352 | 29.5k | delete poRing; |
353 | 120k | } |
354 | | |
355 | 144k | if (peErr != nullptr) |
356 | 101k | { |
357 | 101k | *peErr = bSuccess ? OGRERR_NONE : OGRERR_FAILURE; |
358 | 101k | } |
359 | | |
360 | 144k | return OGRGeometry::ToHandle(poPolygon); |
361 | 151k | } |