Coverage Report

Created: 2026-08-13 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/geos/src/operation/BoundaryOp.cpp
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * GEOS - Geometry Engine Open Source
4
 * http://geos.osgeo.org
5
 *
6
 * Copyright (C) 2022 ISciences LLC
7
 *
8
 * This is free software; you can redistribute and/or modify it under
9
 * the terms of the GNU Lesser General Public Licence as published
10
 * by the Free Software Foundation.
11
 * See the COPYING file for more information.
12
 *
13
 **********************************************************************
14
 *
15
 * Last port: operation/BoundaryOp.java fd5aebb
16
 *
17
 **********************************************************************/
18
19
#include <geos/operation/BoundaryOp.h>
20
#include <geos/algorithm/BoundaryNodeRule.h>
21
#include <geos/geom/Geometry.h>
22
#include <geos/geom/GeometryFactory.h>
23
#include <geos/geom/CompoundCurve.h>
24
#include <geos/geom/Curve.h>
25
#include <geos/geom/LineString.h>
26
#include <geos/geom/MultiCurve.h>
27
#include <geos/geom/MultiLineString.h>
28
#include <geos/geom/SimpleCurve.h>
29
#include <geos/util.h>
30
#include <map>
31
32
using geos::geom::Coordinate;
33
using geos::geom::CoordinateSequence;
34
using geos::geom::Curve;
35
using geos::geom::Dimension;
36
using geos::geom::Geometry;
37
using geos::geom::LineString;
38
using geos::geom::MultiCurve;
39
using geos::geom::MultiLineString;
40
using geos::geom::Point;
41
using geos::algorithm::BoundaryNodeRule;
42
43
namespace geos {
44
namespace operation {
45
46
BoundaryOp::BoundaryOp(const Geometry& geom) :
47
0
    m_geom(geom),
48
0
    m_geomFact(*geom.getFactory()),
49
0
    m_bnRule(BoundaryNodeRule::getBoundaryRuleMod2())
50
0
{}
51
52
BoundaryOp::BoundaryOp(const geom::Geometry& geom, const algorithm::BoundaryNodeRule& bnRule) :
53
0
    m_geom(geom),
54
0
    m_geomFact(*geom.getFactory()),
55
0
    m_bnRule(bnRule)
56
0
{}
57
58
std::unique_ptr<geom::Geometry>
59
BoundaryOp::getBoundary()
60
0
{
61
0
    if (auto ls = dynamic_cast<const Curve*>(&m_geom)) {
62
0
        return boundaryCurve(*ls);
63
0
    }
64
65
0
    if (auto mls = dynamic_cast<const MultiLineString*>(&m_geom)) {
66
0
        return boundaryMultiCurve(*mls);
67
0
    }
68
69
0
    if (auto mc = dynamic_cast<const MultiCurve*>(&m_geom)) {
70
0
        return boundaryMultiCurve(*mc);
71
0
    }
72
73
0
    return m_geom.getBoundary();
74
0
}
75
76
std::unique_ptr<geom::Geometry>
77
BoundaryOp::getBoundary(const geom::Geometry& g)
78
0
{
79
0
    BoundaryOp bop(g);
80
0
    return bop.getBoundary();
81
0
}
82
83
std::unique_ptr<geom::Geometry>
84
BoundaryOp::getBoundary(const geom::Geometry& g, const algorithm::BoundaryNodeRule& bnRule)
85
0
{
86
0
    BoundaryOp bop(g, bnRule);
87
0
    return bop.getBoundary();
88
0
}
89
90
bool
91
BoundaryOp::hasBoundary(const geom::Geometry& geom, const algorithm::BoundaryNodeRule& boundaryNodeRule)
92
0
{
93
    // Note that this does not handle geometry collections with a non-empty linear element
94
0
    if (geom.isEmpty()) {
95
0
        return false;
96
0
    }
97
98
0
    switch (geom.getDimension()) {
99
0
        case Dimension::P: return false;
100
        /**
101
         * Linear geometries might have an empty boundary due to boundary node rule.
102
         */
103
0
        case Dimension::L:
104
0
            {
105
106
0
            auto boundary = getBoundary(geom, boundaryNodeRule);
107
0
            return !boundary->isEmpty();
108
0
            }
109
0
        default:
110
0
            return true;
111
0
        }
112
0
}
113
114
std::unique_ptr<Geometry>
115
BoundaryOp::boundaryCurve(const geom::Curve& line) const
116
0
{
117
0
    if (m_geom.isEmpty()) {
118
0
        return m_geomFact.createMultiPoint();
119
0
    }
120
121
0
    if (line.isClosed()) {
122
        // check whether endpoints of valence 2 are on the boundary or not
123
0
        const bool closedEndpointOnBoundary = m_bnRule.isInBoundary(2);
124
0
        if (closedEndpointOnBoundary) {
125
0
            return line.getStartPoint();
126
0
        }
127
0
        else {
128
0
            return m_geomFact.createMultiPoint();
129
0
        }
130
0
    }
131
132
0
    std::vector<std::unique_ptr<Point>> pts(2);
133
0
    pts[0] = line.getStartPoint();
134
0
    pts[1] = line.getEndPoint();
135
136
0
    return m_geomFact.createMultiPoint(std::move(pts));
137
0
}
138
139
std::unique_ptr<Geometry>
140
BoundaryOp::boundaryMultiCurve(const geom::GeometryCollection& mLine)
141
0
{
142
0
    if (m_geom.isEmpty()) {
143
0
        return m_geomFact.createMultiPoint();
144
0
    }
145
146
0
    auto bdyPts = computeBoundaryCoordinates(mLine);
147
148
    // return Point or MultiPoint
149
0
    if (bdyPts->size() == 1) {
150
0
        return bdyPts->applyAt(0, [this](const auto& c) {
151
0
            return m_geomFact.createPoint(c);
152
0
        });
Unexecuted instantiation: BoundaryOp.cpp:auto geos::operation::BoundaryOp::boundaryMultiCurve(geos::geom::GeometryCollection const&)::$_0::operator()<geos::geom::Coordinate>(geos::geom::Coordinate const&) const
Unexecuted instantiation: BoundaryOp.cpp:auto geos::operation::BoundaryOp::boundaryMultiCurve(geos::geom::GeometryCollection const&)::$_0::operator()<geos::geom::CoordinateXYM>(geos::geom::CoordinateXYM const&) const
Unexecuted instantiation: BoundaryOp.cpp:auto geos::operation::BoundaryOp::boundaryMultiCurve(geos::geom::GeometryCollection const&)::$_0::operator()<geos::geom::CoordinateXYZM>(geos::geom::CoordinateXYZM const&) const
Unexecuted instantiation: BoundaryOp.cpp:auto geos::operation::BoundaryOp::boundaryMultiCurve(geos::geom::GeometryCollection const&)::$_0::operator()<geos::geom::CoordinateXY>(geos::geom::CoordinateXY const&) const
153
0
    }
154
155
    // this handles 0 points case as well
156
0
    return std::unique_ptr<Geometry>(m_geomFact.createMultiPoint(*bdyPts));
157
0
}
158
159
static const CoordinateSequence*
160
getFirstSequence(const Curve& curve)
161
0
{
162
0
    if (curve.getGeometryTypeId() == geom::GEOS_COMPOUNDCURVE) {
163
0
        const auto& cc = static_cast<const geom::CompoundCurve&>(curve);
164
0
        for (std::size_t i = 0; i < cc.getNumCurves(); i++) {
165
0
            if (!cc.getCurveN(i)->isEmpty()) {
166
0
                return cc.getCurveN(i)->getCoordinatesRO();
167
0
            }
168
0
        }
169
170
0
        return nullptr;
171
0
    }
172
173
0
    return static_cast<const geom::SimpleCurve&>(curve).getCoordinatesRO();
174
0
}
175
176
static const CoordinateSequence*
177
getLastSequence(const Curve& curve)
178
0
{
179
0
    if (curve.getGeometryTypeId() == geom::GEOS_COMPOUNDCURVE) {
180
0
        const auto& cc = static_cast<const geom::CompoundCurve&>(curve);
181
0
        for (std::size_t i = cc.getNumCurves(); i != 0; i--) {
182
0
            if (!cc.getCurveN(i-1)->isEmpty()) {
183
0
                return cc.getCurveN(i-1)->getCoordinatesRO();
184
0
            }
185
0
        }
186
187
0
        return nullptr;
188
0
    }
189
190
0
    return static_cast<const geom::SimpleCurve&>(curve).getCoordinatesRO();
191
0
}
192
193
std::unique_ptr<CoordinateSequence>
194
BoundaryOp::computeBoundaryCoordinates(const geom::GeometryCollection& mLine) const
195
0
{
196
0
    assert(mLine.getGeometryTypeId() == geom::GEOS_MULTILINESTRING || mLine.getGeometryTypeId() == geom::GEOS_MULTICURVE);
197
198
0
    auto bdyPts = detail::make_unique<CoordinateSequence>(0, mLine.hasZ(), mLine.hasM());
199
0
    std::map<geom::CoordinateXYZM, int> endpointMap;
200
201
0
    for (std::size_t i = 0; i < mLine.getNumGeometries(); i++) {
202
0
      const Curve* line = detail::down_cast<const Curve*>(mLine.getGeometryN(i));
203
204
0
      if (line->getNumPoints() == 0) {
205
0
        continue;
206
0
      }
207
208
0
      geom::CoordinateXYZM start;
209
0
      geom::CoordinateXYZM end;
210
211
0
      const geom::CoordinateSequence* firstPts = getFirstSequence(*line);
212
0
      const geom::CoordinateSequence* lastPts = getLastSequence(*line);
213
214
0
      firstPts->getAt(0, start);
215
0
      lastPts->getAt(lastPts->size() - 1, end);
216
217
0
      endpointMap[start]++;
218
0
      endpointMap[end]++;
219
0
    }
220
221
0
    for (const auto& [coord, valence] : endpointMap) {
222
0
        if (m_bnRule.isInBoundary(valence)) {
223
0
            bdyPts->add(coord);
224
0
        }
225
0
    }
226
227
0
    return bdyPts;
228
0
}
229
230
231
}
232
}