Coverage Report

Created: 2026-02-14 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/valid/IsValidOp.cpp
Line
Count
Source
1
/**********************************************************************
2
*
3
* GEOS - Geometry Engine Open Source
4
* http://geos.osgeo.org
5
*
6
* Copyright (C) 2021 Paul Ramsey <pramsey@cleverelephant.ca>
7
* Copyright (C) 2021 Martin Davis
8
*
9
* This is free software; you can redistribute and/or modify it under
10
* the terms of the GNU Lesser General Public Licence as published
11
* by the Free Software Foundation.
12
* See the COPYING file for more information.
13
*
14
**********************************************************************/
15
16
#include <geos/algorithm/locate/IndexedPointInAreaLocator.h>
17
#include <geos/geom/Coordinate.h>
18
#include <geos/geom/Geometry.h>
19
#include <geos/geom/GeometryCollection.h>
20
#include <geos/geom/LineString.h>
21
#include <geos/geom/LinearRing.h>
22
#include <geos/geom/Location.h>
23
#include <geos/geom/MultiPoint.h>
24
#include <geos/geom/MultiPolygon.h>
25
#include <geos/geom/Point.h>
26
#include <geos/geom/Polygon.h>
27
#include <geos/operation/valid/IsValidOp.h>
28
#include <geos/operation/valid/IndexedNestedHoleTester.h>
29
#include <geos/operation/valid/IndexedNestedPolygonTester.h>
30
#include <geos/util/UnsupportedOperationException.h>
31
#include <geos/util/IllegalArgumentException.h>
32
33
#include <cmath>
34
35
using namespace geos::geom;
36
using geos::algorithm::locate::IndexedPointInAreaLocator;
37
38
namespace geos {      // geos
39
namespace operation { // geos.operation
40
namespace valid {     // geos.operation.valid
41
42
/* public */
43
bool
44
IsValidOp::isValid()
45
0
{
46
0
    return isValidGeometry(inputGeometry);
47
0
}
48
49
50
/* public static */
51
bool
52
IsValidOp::isValid(const CoordinateXY* coord)
53
0
{
54
0
    if (std::isfinite(coord->x) && std::isfinite(coord->y)) {
55
0
        return true;
56
0
    }
57
0
    else {
58
0
        return false;
59
0
    }
60
0
}
61
62
63
/* public */
64
const TopologyValidationError *
65
IsValidOp::getValidationError()
66
0
{
67
0
    isValidGeometry(inputGeometry);
68
0
    return validErr.get();
69
0
}
70
71
void
72
IsValidOp::logInvalid(int code, const CoordinateXY& pt)
73
0
{
74
0
    validErr = detail::make_unique<TopologyValidationError>(code, pt);
75
0
}
76
77
/* private */
78
bool
79
IsValidOp::isValidGeometry(const Geometry* g)
80
0
{
81
0
    validErr.reset(nullptr);
82
83
0
    if (!g)
84
0
        throw util::IllegalArgumentException("Null geometry argument to IsValidOp");
85
86
    // empty geometries are always valid
87
0
    if (g->isEmpty()) return true;
88
0
    switch (g->getGeometryTypeId()) {
89
0
        case GEOS_POINT:
90
0
            return isValid(static_cast<const Point*>(g));
91
0
        case GEOS_MULTIPOINT:
92
0
            return isValid(static_cast<const MultiPoint*>(g));
93
0
        case GEOS_LINEARRING:
94
0
            return isValid(static_cast<const LinearRing*>(g));
95
0
        case GEOS_LINESTRING:
96
0
            return isValid(static_cast<const LineString*>(g));
97
0
        case GEOS_POLYGON:
98
0
            return isValid(static_cast<const Polygon*>(g));
99
0
        case GEOS_MULTIPOLYGON:
100
0
            return isValid(static_cast<const MultiPolygon*>(g));
101
0
        case GEOS_MULTILINESTRING:
102
0
            return isValid(static_cast<const GeometryCollection*>(g));
103
0
        case GEOS_GEOMETRYCOLLECTION:
104
0
            return isValid(static_cast<const GeometryCollection*>(g));
105
0
        case GEOS_CIRCULARSTRING:
106
0
        case GEOS_COMPOUNDCURVE:
107
0
        case GEOS_CURVEPOLYGON:
108
0
        case GEOS_MULTICURVE:
109
0
        case GEOS_MULTISURFACE:
110
0
            throw util::UnsupportedOperationException("Curved types not supported in IsValidOp.");
111
0
    }
112
113
    // geometry type not known
114
0
    throw util::UnsupportedOperationException(g->getGeometryType());
115
0
}
116
117
118
/* private */
119
bool
120
IsValidOp::isValid(const Point* g)
121
0
{
122
0
    checkCoordinatesValid(g->getCoordinatesRO());
123
0
    if (hasInvalidError()) return false;
124
0
    return true;
125
0
}
126
127
128
/* private */
129
bool
130
IsValidOp::isValid(const MultiPoint* g)
131
0
{
132
0
    for (std::size_t i = 0; i < g->getNumGeometries(); i++) {
133
0
        const Point* p = g->getGeometryN(i);
134
0
        if (p->isEmpty()) continue;
135
0
        if (!isValid(p->getCoordinate())) {
136
0
            logInvalid(TopologyValidationError::eInvalidCoordinate,
137
0
                       *(p->getCoordinate()));
138
0
            return false;;
139
0
        }
140
0
    }
141
0
    return true;
142
0
}
143
144
145
/* private */
146
bool
147
IsValidOp::isValid(const LineString* g)
148
0
{
149
0
    checkCoordinatesValid(g->getCoordinatesRO());
150
0
    if (hasInvalidError()) return false;
151
152
0
    checkTooFewPoints(g, MIN_SIZE_LINESTRING);
153
0
    if (hasInvalidError()) return false;
154
155
0
    return true;
156
0
}
157
158
159
/* private */
160
bool
161
IsValidOp::isValid(const LinearRing* g)
162
0
{
163
0
    checkCoordinatesValid(g->getCoordinatesRO());
164
0
    if (hasInvalidError()) return false;
165
166
0
    checkRingClosed(g);
167
0
    if (hasInvalidError()) return false;
168
169
0
    checkRingPointSize(g);
170
0
    if (hasInvalidError()) return false;
171
172
0
    checkRingSimple(g);
173
0
    if (hasInvalidError()) return false;
174
175
0
    return true;
176
0
}
177
178
179
/* private */
180
bool
181
IsValidOp::isValid(const Polygon* g)
182
0
{
183
0
    checkCoordinatesValid(g);
184
0
    if (hasInvalidError()) return false;
185
186
0
    checkRingsClosed(g);
187
0
    if (hasInvalidError()) return false;
188
189
0
    checkRingsPointSize(g);
190
0
    if (hasInvalidError()) return false;
191
192
0
    PolygonTopologyAnalyzer areaAnalyzer(g, isInvertedRingValid);
193
194
0
    checkAreaIntersections(areaAnalyzer);
195
0
    if (hasInvalidError()) return false;
196
197
0
    checkHolesInShell(g);
198
0
    if (hasInvalidError()) return false;
199
200
0
    checkHolesNotNested(g);
201
0
    if (hasInvalidError()) return false;
202
203
0
    checkInteriorConnected(areaAnalyzer);
204
0
    if (hasInvalidError()) return false;
205
206
0
    return true;
207
0
}
208
209
210
/* private */
211
bool
212
IsValidOp::isValid(const MultiPolygon* g)
213
0
{
214
0
    for (std::size_t i = 0; i < g->getNumGeometries(); i++) {
215
0
        const Polygon* p = g->getGeometryN(i);
216
0
        checkCoordinatesValid(p);
217
0
        if (hasInvalidError()) return false;
218
219
0
        checkRingsClosed(p);
220
0
        if (hasInvalidError()) return false;
221
222
0
        checkRingsPointSize(p);
223
0
        if (hasInvalidError()) return false;
224
0
    }
225
226
0
    PolygonTopologyAnalyzer areaAnalyzer(g, isInvertedRingValid);
227
228
0
    checkAreaIntersections(areaAnalyzer);
229
0
    if (hasInvalidError()) return false;
230
231
0
    for (std::size_t i = 0; i < g->getNumGeometries(); i++) {
232
0
        const Polygon* p = g->getGeometryN(i);
233
0
        checkHolesInShell(p);
234
0
        if (hasInvalidError()) return false;
235
0
    }
236
237
0
    for (std::size_t i = 0; i < g->getNumGeometries(); i++) {
238
0
        const Polygon* p = g->getGeometryN(i);
239
0
        checkHolesNotNested(p);
240
0
        if (hasInvalidError()) return false;
241
0
    }
242
243
0
    checkShellsNotNested(g);
244
0
    if (hasInvalidError()) return false;
245
246
0
    checkInteriorConnected(areaAnalyzer);
247
0
    if (hasInvalidError()) return false;
248
249
0
    return true;
250
0
}
251
252
253
/* private */
254
bool
255
IsValidOp::isValid(const GeometryCollection* gc)
256
0
{
257
0
    for (std::size_t i = 0; i < gc->getNumGeometries(); i++) {
258
0
        if (! isValidGeometry(gc->getGeometryN(i)))
259
0
            return false;
260
0
    }
261
0
    return true;
262
0
}
263
264
265
/* private */
266
void
267
IsValidOp::checkCoordinatesValid(const CoordinateSequence* coords)
268
0
{
269
0
    for (std::size_t i = 0; i < coords->size(); i++) {
270
0
        if (! isValid(coords->getAt<CoordinateXY>(i))) {
271
0
            logInvalid(TopologyValidationError::eInvalidCoordinate,
272
0
                       coords->getAt<CoordinateXY>(i));
273
0
            return;
274
0
        }
275
0
    }
276
0
}
277
278
279
/* private */
280
void
281
IsValidOp::checkCoordinatesValid(const Polygon* poly)
282
0
{
283
0
    checkCoordinatesValid(poly->getExteriorRing()->getCoordinatesRO());
284
0
    if (hasInvalidError()) return;
285
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
286
0
        checkCoordinatesValid(poly->getInteriorRingN(i)->getCoordinatesRO());
287
0
        if (hasInvalidError()) return;
288
0
    }
289
0
}
290
291
292
/* private */
293
void
294
IsValidOp::checkRingClosed(const LinearRing* ring)
295
0
{
296
0
    if (ring->isEmpty()) return;
297
0
    if (! ring->isClosed()) {
298
0
        Coordinate pt = ring->getNumPoints() >= 1
299
0
                        ? ring->getCoordinateN(0)
300
0
                        : Coordinate();
301
0
        logInvalid(TopologyValidationError::eRingNotClosed, pt);
302
0
        return;
303
0
    }
304
0
}
305
306
307
/* private */
308
void
309
IsValidOp::checkRingsClosed(const Polygon* poly)
310
0
{
311
0
    checkRingClosed(poly->getExteriorRing());
312
0
    if (hasInvalidError()) return;
313
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
314
0
        checkRingClosed(poly->getInteriorRingN(i));
315
0
        if (hasInvalidError()) return;
316
0
    }
317
0
}
318
319
320
/* private */
321
void
322
IsValidOp::checkRingsPointSize(const Polygon* poly)
323
0
{
324
0
    checkRingPointSize(poly->getExteriorRing());
325
0
    if (hasInvalidError()) return;
326
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
327
0
        checkRingPointSize(poly->getInteriorRingN(i));
328
0
        if (hasInvalidError()) return;
329
0
    }
330
0
}
331
332
333
/* private */
334
void
335
IsValidOp::checkRingPointSize(const LinearRing* ring)
336
0
{
337
0
    if (ring->isEmpty()) return;
338
0
    checkTooFewPoints(ring, MIN_SIZE_RING);
339
0
}
340
341
342
/* private */
343
void
344
IsValidOp::checkTooFewPoints(const LineString* line, std::size_t minSize)
345
0
{
346
0
    if (! isNonRepeatedSizeAtLeast(line, minSize) ) {
347
0
        CoordinateXY pt = line->getNumPoints() >= 1
348
0
                        ? line->getCoordinatesRO()->getAt<CoordinateXY>(0)
349
0
                        : Coordinate();
350
0
        logInvalid(TopologyValidationError::eTooFewPoints, pt);
351
0
    }
352
0
}
353
354
355
/* private */
356
bool
357
IsValidOp::isNonRepeatedSizeAtLeast(const LineString* line, std::size_t minSize)
358
0
{
359
0
    std::size_t numPts = 0;
360
0
    const CoordinateXY* prevPt = nullptr;
361
0
    const CoordinateSequence& seq = *line->getCoordinatesRO();
362
0
    for (std::size_t i = 0; i < seq.size(); i++) {
363
0
        if (numPts >= minSize) return true;
364
0
        const CoordinateXY& pt = seq.getAt<CoordinateXY>(i);
365
0
        if (prevPt == nullptr || ! pt.equals2D(*prevPt))
366
0
            numPts++;
367
0
        prevPt = &pt;
368
0
    }
369
0
    return numPts >= minSize;
370
0
}
371
372
373
/* private */
374
void
375
IsValidOp::checkAreaIntersections(PolygonTopologyAnalyzer& areaAnalyzer)
376
0
{
377
0
    if (areaAnalyzer.hasInvalidIntersection()) {
378
0
        logInvalid(areaAnalyzer.getInvalidCode(),
379
0
                   areaAnalyzer.getInvalidLocation());
380
0
    }
381
0
}
382
383
384
/* private */
385
void
386
IsValidOp::checkRingSimple(const LinearRing* ring)
387
0
{
388
0
    CoordinateXY intPt = PolygonTopologyAnalyzer::findSelfIntersection(ring);
389
0
    if (! intPt.isNull()) {
390
0
        logInvalid(TopologyValidationError::eRingSelfIntersection,
391
0
            intPt);
392
0
    }
393
0
}
394
395
396
/* private */
397
void
398
IsValidOp::checkHolesInShell(const Polygon* poly)
399
0
{
400
    // skip test if no holes are present
401
0
    if (poly->getNumInteriorRing() <= 0) return;
402
403
0
    const LinearRing* shell = poly->getExteriorRing();
404
0
    bool isShellEmpty = shell->isEmpty();
405
406
0
    for (std::size_t i = 0; i < poly->getNumInteriorRing(); i++) {
407
0
        const LinearRing* hole = poly->getInteriorRingN(i);
408
0
        if (hole->isEmpty()) continue;
409
410
0
        const CoordinateXY* invalidPt = nullptr;
411
0
        if (isShellEmpty) {
412
0
            invalidPt = hole->getCoordinate();
413
0
        }
414
0
        else {
415
0
            invalidPt = findHoleOutsideShellPoint(hole, shell);
416
0
        }
417
0
        if (invalidPt != nullptr) {
418
0
            logInvalid(
419
0
                TopologyValidationError::eHoleOutsideShell,
420
0
                *invalidPt);
421
0
            return;
422
0
        }
423
0
    }
424
0
}
425
426
427
/* private */
428
const CoordinateXY*
429
IsValidOp::findHoleOutsideShellPoint(const LinearRing* hole, const LinearRing* shell)
430
0
{
431
0
    const CoordinateXY& holePt0 = hole->getCoordinatesRO()->getAt<CoordinateXY>(0);
432
    /**
433
     * If hole envelope is not covered by shell, it must be outside
434
     */
435
0
    if (! shell->getEnvelopeInternal()->covers(hole->getEnvelopeInternal()))
436
0
        return &holePt0;
437
438
0
    if (PolygonTopologyAnalyzer::isRingNested(hole, shell))
439
0
        return nullptr;
440
0
    return &holePt0;
441
0
}
442
443
444
/* private */
445
void
446
IsValidOp::checkHolesNotNested(const Polygon* poly)
447
0
{
448
    // skip test if no holes are present
449
0
    if (poly->getNumInteriorRing() <= 0) return;
450
451
0
    IndexedNestedHoleTester nestedTester(poly);
452
0
    if (nestedTester.isNested()) {
453
0
        logInvalid(TopologyValidationError::eNestedHoles,
454
0
                   nestedTester.getNestedPoint());
455
0
    }
456
0
}
457
458
459
/* private */
460
void
461
IsValidOp::checkShellsNotNested(const MultiPolygon* mp)
462
0
{
463
    // skip test if only one shell present
464
0
    if (mp->getNumGeometries() <= 1)
465
0
        return;
466
467
0
    IndexedNestedPolygonTester nestedTester(mp);
468
0
    if (nestedTester.isNested()) {
469
0
        logInvalid(TopologyValidationError::eNestedShells,
470
0
                   nestedTester.getNestedPoint());
471
0
    }
472
0
}
473
474
475
/* private */
476
void
477
IsValidOp::checkInteriorConnected(PolygonTopologyAnalyzer& analyzer)
478
0
{
479
0
    if (analyzer.isInteriorDisconnected())
480
0
        logInvalid(TopologyValidationError::eDisconnectedInterior,
481
0
                   analyzer.getDisconnectionLocation());
482
0
}
483
484
485
} // namespace geos.operation.valid
486
} // namespace geos.operation
487
} // namespace geos