Coverage Report

Created: 2025-10-28 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/geomgraph/EdgeEndStar.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2011 Sandro Santilli <strk@kbt.io>
7
 * Copyright (C) 2005-2006 Refractions Research Inc.
8
 * Copyright (C) 2001-2002 Vivid Solutions Inc.
9
 *
10
 * This is free software; you can redistribute and/or modify it under
11
 * the terms of the GNU Lesser General Public Licence as published
12
 * by the Free Software Foundation.
13
 * See the COPYING file for more information.
14
 *
15
 **********************************************************************
16
 *
17
 * Last port: geomgraph/EdgeEndStar.java r428 (JTS-1.12+)
18
 *
19
 **********************************************************************/
20
21
#include <geos/util/TopologyException.h>
22
#include <geos/geomgraph/EdgeEndStar.h>
23
#include <geos/algorithm/locate/SimplePointInAreaLocator.h>
24
#include <geos/geom/Location.h>
25
#include <geos/geomgraph/Label.h>
26
#include <geos/geom/Position.h>
27
#include <geos/geomgraph/GeometryGraph.h>
28
29
#include <cassert>
30
#include <string>
31
#include <vector>
32
#include <sstream>
33
34
#ifndef GEOS_DEBUG
35
#define GEOS_DEBUG 0
36
#endif
37
38
using namespace geos::geom;
39
40
namespace geos {
41
namespace geomgraph { // geos.geomgraph
42
43
/*public*/
44
EdgeEndStar::EdgeEndStar()
45
    :
46
0
    edgeMap()
47
0
{
48
0
    ptInAreaLocation[0] = Location::NONE;
49
0
    ptInAreaLocation[1] = Location::NONE;
50
0
}
51
52
/*public*/
53
Coordinate&
54
EdgeEndStar::getCoordinate()
55
0
{
56
0
    static Coordinate nullCoord(DoubleNotANumber, DoubleNotANumber, DoubleNotANumber);
57
0
    if(edgeMap.empty()) {
58
0
        return nullCoord;
59
0
    }
60
61
0
    EdgeEndStar::iterator it = begin();
62
0
    EdgeEnd* e = *it;
63
0
    assert(e);
64
0
    return e->getCoordinate();
65
0
}
66
67
/*public*/
68
const Coordinate&
69
EdgeEndStar::getCoordinate() const
70
0
{
71
0
    return const_cast<EdgeEndStar*>(this)->getCoordinate();
72
0
}
73
74
/*public*/
75
EdgeEnd*
76
EdgeEndStar::getNextCW(EdgeEnd* ee)
77
0
{
78
0
    EdgeEndStar::iterator it = find(ee);
79
0
    if(it == end()) {
80
0
        return nullptr;
81
0
    }
82
0
    if(it == begin()) {
83
0
        it = end();
84
0
        --it;
85
0
    }
86
0
    else {
87
0
        --it;
88
0
    }
89
0
    return *it;
90
0
}
91
92
/*public*/
93
void
94
EdgeEndStar::computeLabelling(const std::vector<std::unique_ptr<GeometryGraph>>& geomGraph)
95
//throw(TopologyException *)
96
0
{
97
0
    computeEdgeEndLabels(geomGraph[0]->getBoundaryNodeRule());
98
99
    // Propagate side labels  around the edges in the star
100
    // for each parent Geometry
101
    //
102
    // these calls can throw a TopologyException
103
0
    propagateSideLabels(0);
104
0
    propagateSideLabels(1);
105
106
    /*
107
     * If there are edges that still have null labels for a geometry
108
     * this must be because there are no area edges for that geometry
109
     * incident on this node.
110
     * In this case, to label the edge for that geometry we must test
111
     * whether the edge is in the interior of the geometry.
112
     * To do this it suffices to determine whether the node for the
113
     * edge is in the interior of an area.
114
     * If so, the edge has location INTERIOR for the geometry.
115
     * In all other cases (e.g. the node is on a line, on a point, or
116
     * not on the geometry at all) the edge
117
     * has the location EXTERIOR for the geometry.
118
     *
119
     * Note that the edge cannot be on the BOUNDARY of the geometry,
120
     * since then there would have been a parallel edge from the
121
     * Geometry at this node also labelled BOUNDARY
122
     * and this edge would have been labelled in the previous step.
123
     *
124
     * This code causes a problem when dimensional collapses are present,
125
     * since it may try and determine the location of a node where a
126
     * dimensional collapse has occurred.
127
     * The point should be considered to be on the EXTERIOR
128
     * of the polygon, but locate() will return INTERIOR, since it is
129
     * passed the original Geometry, not the collapsed version.
130
     *
131
     * If there are incident edges which are Line edges labelled BOUNDARY,
132
     * then they must be edges resulting from dimensional collapses.
133
     * In this case the other edges can be labelled EXTERIOR for this
134
     * Geometry.
135
     *
136
     * MD 8/11/01 - NOT TRUE!  The collapsed edges may in fact be in the
137
     * interior of the Geometry, which means the other edges should be
138
     * labelled INTERIOR for this Geometry.
139
     * Not sure how solve this...  Possibly labelling needs to be split
140
     * into several phases:
141
     * area label propagation, symLabel merging, then finally null label
142
     * resolution.
143
     */
144
0
    bool hasDimensionalCollapseEdge[2] = {false, false};
145
146
0
    for(EdgeEnd* e : *this) {
147
0
        assert(e);
148
0
        const Label& label = e->getLabel();
149
0
        for(uint8_t geomi = 0; geomi < 2; geomi++) {
150
0
            if(label.isLine(geomi) && label.getLocation(geomi) == Location::BOUNDARY) {
151
0
                hasDimensionalCollapseEdge[geomi] = true;
152
0
            }
153
0
        }
154
0
    }
155
156
0
    for(EdgeEnd* e : *this) {
157
0
        assert(e);
158
0
        Label& label = e->getLabel();
159
0
        for(uint32_t geomi = 0; geomi < 2; ++geomi) {
160
0
            if(label.isAnyNull(geomi)) {
161
0
                Location loc = Location::EXTERIOR;
162
0
                if(!hasDimensionalCollapseEdge[geomi]) {
163
0
                    Coordinate& p = e->getCoordinate();
164
0
                    loc = getLocation(geomi, p, geomGraph);
165
0
                }
166
0
                label.setAllLocationsIfNull(geomi, loc);
167
0
            }
168
0
        }
169
0
    }
170
0
}
171
172
/*private*/
173
void
174
EdgeEndStar::computeEdgeEndLabels(
175
    const algorithm::BoundaryNodeRule& boundaryNodeRule)
176
0
{
177
    // Compute edge label for each EdgeEnd
178
0
    for(EdgeEndStar::iterator it = begin(); it != end(); ++it) {
179
0
        EdgeEnd* ee = *it;
180
0
        assert(ee);
181
0
        ee->computeLabel(boundaryNodeRule);
182
0
    }
183
0
}
184
185
/*public*/
186
Location
187
EdgeEndStar::getLocation(uint32_t geomIndex,
188
                         const Coordinate& p, const std::vector<std::unique_ptr<GeometryGraph>>& geom)
189
0
{
190
    // compute location only on demand
191
0
    if(ptInAreaLocation[geomIndex] == Location::NONE) {
192
0
        ptInAreaLocation[geomIndex] = algorithm::locate::SimplePointInAreaLocator::locate(p,
193
0
                                      geom[geomIndex]->getGeometry());
194
0
    }
195
0
    return ptInAreaLocation[geomIndex];
196
0
}
197
198
/*public*/
199
bool
200
EdgeEndStar::isAreaLabelsConsistent(const GeometryGraph& geomGraph)
201
0
{
202
0
    computeEdgeEndLabels(geomGraph.getBoundaryNodeRule());
203
0
    return checkAreaLabelsConsistent(0);
204
0
}
205
206
/*private*/
207
bool
208
EdgeEndStar::checkAreaLabelsConsistent(uint32_t geomIndex)
209
0
{
210
    // Since edges are stored in CCW order around the node,
211
    // As we move around the ring we move from the right to
212
    // the left side of the edge
213
214
    // if no edges, trivially consistent
215
0
    if(edgeMap.empty()) {
216
0
        return true;
217
0
    }
218
219
    // initialize startLoc to location of last L side (if any)
220
0
    assert(*rbegin());
221
0
    const Label& startLabel = (*rbegin())->getLabel();
222
0
    Location startLoc = startLabel.getLocation(geomIndex, Position::LEFT);
223
224
    // Found unlabelled area edge
225
0
    assert(startLoc != Location::NONE);
226
227
0
    Location currLoc = startLoc;
228
229
0
    for(EdgeEndStar::iterator it = begin(), itEnd = end(); it != itEnd; ++it) {
230
0
        EdgeEnd* e = *it;
231
0
        assert(e);
232
0
        const Label& eLabel = e->getLabel();
233
234
        // we assume that we are only checking a area
235
236
        // Found non-area edge
237
0
        assert(eLabel.isArea(geomIndex));
238
239
0
        Location leftLoc = eLabel.getLocation(geomIndex, Position::LEFT);
240
0
        Location rightLoc = eLabel.getLocation(geomIndex, Position::RIGHT);
241
        // check that edge is really a boundary between inside and outside!
242
0
        if(leftLoc == rightLoc) {
243
0
            return false;
244
0
        }
245
        // check side location conflict
246
        //assert(rightLoc == currLoc); // "side location conflict " + locStr);
247
0
        if(rightLoc != currLoc) {
248
0
            return false;
249
0
        }
250
0
        currLoc = leftLoc;
251
0
    }
252
0
    return true;
253
0
}
254
255
/*public*/
256
void
257
EdgeEndStar::propagateSideLabels(uint32_t geomIndex)
258
//throw(TopologyException *)
259
0
{
260
    // Since edges are stored in CCW order around the node,
261
    // As we move around the ring we move from the right to the
262
    // left side of the edge
263
0
    Location startLoc = Location::NONE;
264
265
0
    EdgeEndStar::iterator beginIt = begin();
266
0
    EdgeEndStar::iterator endIt = end();
267
0
    EdgeEndStar::iterator it;
268
269
    // initialize loc to location of last L side (if any)
270
0
    for(it = beginIt; it != endIt; ++it) {
271
0
        EdgeEnd* e = *it;
272
0
        assert(e);
273
0
        const Label& label = e->getLabel();
274
0
        if(label.isArea(geomIndex) &&
275
0
                label.getLocation(geomIndex, Position::LEFT) != Location::NONE) {
276
0
            startLoc = label.getLocation(geomIndex, Position::LEFT);
277
0
        }
278
0
    }
279
280
    // no labelled sides found, so no labels to propagate
281
0
    if(startLoc == Location::NONE) {
282
0
        return;
283
0
    }
284
285
0
    Location currLoc = startLoc;
286
0
    for(it = beginIt; it != endIt; ++it) {
287
0
        EdgeEnd* e = *it;
288
0
        assert(e);
289
0
        Label& label = e->getLabel();
290
        // set null ON values to be in current location
291
0
        if(label.getLocation(geomIndex, Position::ON) == Location::NONE) {
292
0
            label.setLocation(geomIndex, Position::ON, currLoc);
293
0
        }
294
295
        // set side labels (if any)
296
        // if (label.isArea())  //ORIGINAL
297
0
        if(label.isArea(geomIndex)) {
298
0
            Location leftLoc = label.getLocation(geomIndex,
299
0
                                                 Position::LEFT);
300
301
0
            Location rightLoc = label.getLocation(geomIndex,
302
0
                                                  Position::RIGHT);
303
304
            // if there is a right location, that is the next
305
            // location to propagate
306
0
            if(rightLoc != Location::NONE) {
307
0
                if(rightLoc != currLoc) {
308
0
                    std::stringstream ss;
309
0
                    ss << "side location conflict at ";
310
0
                    ss << e->getCoordinate().toString();
311
0
                    ss << ". This can occur if the input geometry is invalid.";
312
0
                    throw util::TopologyException(ss.str());
313
0
                }
314
0
                if(leftLoc == Location::NONE) {
315
                    // found single null side at e->getCoordinate()
316
0
                    assert(0);
317
0
                }
318
0
                currLoc = leftLoc;
319
0
            }
320
0
            else {
321
                /*
322
                 * RHS is null - LHS must be null too.
323
                 * This must be an edge from the other
324
                 * geometry, which has no location
325
                 * labelling for this geometry.
326
                 * This edge must lie wholly inside or
327
                 * outside the other geometry (which is
328
                 * determined by the current location).
329
                 * Assign both sides to be the current
330
                 * location.
331
                 */
332
                // found single null side
333
0
                assert(label.getLocation(geomIndex,
334
0
                                         Position::LEFT) == Location::NONE);
335
336
0
                label.setLocation(geomIndex, Position::RIGHT,
337
0
                                  currLoc);
338
0
                label.setLocation(geomIndex, Position::LEFT,
339
0
                                  currLoc);
340
0
            }
341
0
        }
342
0
    }
343
0
}
344
345
/*public*/
346
std::string
347
EdgeEndStar::print() const
348
0
{
349
0
    std::ostringstream s;
350
0
    s << *this;
351
0
    return s.str();
352
0
}
353
354
std::ostream&
355
operator<< (std::ostream& os, const EdgeEndStar& es)
356
0
{
357
0
    os << "EdgeEndStar:   " << es.getCoordinate() << "\n";
358
0
    for(EdgeEndStar::const_iterator it = es.begin(), itEnd = es.end(); it != itEnd; ++it) {
359
0
        const EdgeEnd* e = *it;
360
        assert(e);
361
0
        os << *e;
362
0
    }
363
0
    return os;
364
0
}
365
366
} // namespace geos.geomgraph
367
} // namespace geos