Coverage Report

Created: 2025-08-11 09:23

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