/src/gdal/apps/gdalalg_vector_check_coverage.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: "gdal vector check-coverage" subcommand |
5 | | * Author: Daniel Baston |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2025, ISciences LLC |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdalalg_vector_check_coverage.h" |
14 | | |
15 | | #include "cpl_error.h" |
16 | | #include "gdal_priv.h" |
17 | | #include "gdalalg_vector_geom.h" |
18 | | #include "ogr_geometry.h" |
19 | | #include "ogr_geos.h" |
20 | | |
21 | | #include <cinttypes> |
22 | | |
23 | | #ifndef _ |
24 | 0 | #define _(x) (x) |
25 | | #endif |
26 | | |
27 | | //! @cond Doxygen_Suppress |
28 | | |
29 | | GDALVectorCheckCoverageAlgorithm::GDALVectorCheckCoverageAlgorithm( |
30 | | bool standaloneStep) |
31 | 0 | : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL, |
32 | 0 | standaloneStep) |
33 | 0 | { |
34 | 0 | AddArg("include-valid", 0, |
35 | 0 | _("Include valid inputs in output, with empty geometry"), |
36 | 0 | &m_includeValid); |
37 | |
|
38 | 0 | AddArg("geometry-field", 0, _("Name of geometry field to check"), |
39 | 0 | &m_geomField); |
40 | |
|
41 | 0 | AddArg("maximum-gap-width", 0, _("Maximum width of a gap to be flagged"), |
42 | 0 | &m_maximumGapWidth) |
43 | 0 | .SetMinValueIncluded(0); |
44 | 0 | } |
45 | | |
46 | | #if defined HAVE_GEOS && \ |
47 | | (GEOS_VERSION_MAJOR > 3 || \ |
48 | | (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12)) |
49 | | |
50 | | class GDALVectorCheckCoverageOutputLayer final |
51 | | : public GDALGeosNonStreamingAlgorithmLayer |
52 | | { |
53 | | public: |
54 | | explicit GDALVectorCheckCoverageOutputLayer(OGRLayer &srcLayer, |
55 | | int geomFieldIndex, |
56 | | const std::string &name, |
57 | | double maximumGapWidth, |
58 | | bool includeValid) |
59 | | : GDALGeosNonStreamingAlgorithmLayer(srcLayer, geomFieldIndex), |
60 | | m_defn(OGRFeatureDefn::CreateFeatureDefn(name.c_str())), |
61 | | m_maximumGapWidth(maximumGapWidth), m_includeValid(includeValid) |
62 | | { |
63 | | m_defn->Reference(); |
64 | | |
65 | | const OGRFeatureDefn *poSrcLayerDefn = srcLayer.GetLayerDefn(); |
66 | | m_defn->SetGeomType(wkbMultiLineString); |
67 | | m_defn->GetGeomFieldDefn(0)->SetSpatialRef( |
68 | | poSrcLayerDefn->GetGeomFieldDefn(geomFieldIndex)->GetSpatialRef()); |
69 | | } |
70 | | |
71 | | ~GDALVectorCheckCoverageOutputLayer() override; |
72 | | |
73 | | const OGRFeatureDefn *GetLayerDefn() const override |
74 | | { |
75 | | return m_defn; |
76 | | } |
77 | | |
78 | | int TestCapability(const char *) const override |
79 | | { |
80 | | return false; |
81 | | } |
82 | | |
83 | | bool PolygonsOnly() const override |
84 | | { |
85 | | return true; |
86 | | } |
87 | | |
88 | | bool SkipEmpty() const override |
89 | | { |
90 | | return !m_includeValid; |
91 | | } |
92 | | |
93 | | bool ProcessGeos() override |
94 | | { |
95 | | // Perform coverage checking |
96 | | GEOSGeometry *coll = GEOSGeom_createCollection_r( |
97 | | m_poGeosContext, GEOS_GEOMETRYCOLLECTION, m_apoGeosInputs.data(), |
98 | | static_cast<unsigned int>(m_apoGeosInputs.size())); |
99 | | |
100 | | if (coll == nullptr) |
101 | | { |
102 | | return false; |
103 | | } |
104 | | |
105 | | m_apoGeosInputs.clear(); |
106 | | |
107 | | int geos_result = |
108 | | GEOSCoverageIsValid_r(m_poGeosContext, coll, m_maximumGapWidth, |
109 | | &m_poGeosResultAsCollection); |
110 | | GEOSGeom_destroy_r(m_poGeosContext, coll); |
111 | | |
112 | | CPLDebug("CoverageIsValid", "%d", geos_result); |
113 | | |
114 | | return geos_result != 2; |
115 | | } |
116 | | |
117 | | private: |
118 | | OGRFeatureDefn *m_defn; |
119 | | const double m_maximumGapWidth; |
120 | | const bool m_includeValid; |
121 | | |
122 | | CPL_DISALLOW_COPY_ASSIGN(GDALVectorCheckCoverageOutputLayer) |
123 | | }; |
124 | | |
125 | | GDALVectorCheckCoverageOutputLayer::~GDALVectorCheckCoverageOutputLayer() |
126 | | { |
127 | | if (m_defn != nullptr) |
128 | | { |
129 | | m_defn->Release(); |
130 | | } |
131 | | } |
132 | | |
133 | | bool GDALVectorCheckCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
134 | | { |
135 | | auto poSrcDS = m_inputDataset[0].GetDatasetRef(); |
136 | | auto poDstDS = std::make_unique<GDALVectorNonStreamingAlgorithmDataset>(); |
137 | | |
138 | | const bool bSingleLayerOutput = m_inputLayerNames.empty() |
139 | | ? poSrcDS->GetLayerCount() == 1 |
140 | | : m_inputLayerNames.size() == 1; |
141 | | |
142 | | GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt); |
143 | | |
144 | | for (auto &&poSrcLayer : poSrcDS->GetLayers()) |
145 | | { |
146 | | if (m_inputLayerNames.empty() || |
147 | | std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(), |
148 | | poSrcLayer->GetDescription()) != m_inputLayerNames.end()) |
149 | | { |
150 | | const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn(); |
151 | | if (poSrcLayerDefn->GetGeomFieldCount() == 0) |
152 | | { |
153 | | if (m_inputLayerNames.empty()) |
154 | | continue; |
155 | | ReportError(CE_Failure, CPLE_AppDefined, |
156 | | "Specified layer '%s' has no geometry field", |
157 | | poSrcLayer->GetDescription()); |
158 | | return false; |
159 | | } |
160 | | |
161 | | progressHelper.AddProcessedLayer(*poSrcLayer); |
162 | | } |
163 | | } |
164 | | |
165 | | for ([[maybe_unused]] auto [poSrcLayer, bProcessed, layerProgressFunc, |
166 | | layerProgressData] : progressHelper) |
167 | | { |
168 | | const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn(); |
169 | | const int geomFieldIndex = |
170 | | m_geomField.empty() |
171 | | ? 0 |
172 | | : poSrcLayerDefn->GetGeomFieldIndex(m_geomField.c_str()); |
173 | | |
174 | | if (geomFieldIndex == -1) |
175 | | { |
176 | | ReportError(CE_Failure, CPLE_AppDefined, |
177 | | "Specified geometry field '%s' does not exist in " |
178 | | "layer '%s'", |
179 | | m_geomField.c_str(), poSrcLayer->GetDescription()); |
180 | | return false; |
181 | | } |
182 | | |
183 | | std::string layerName = bSingleLayerOutput |
184 | | ? "invalid_edge" |
185 | | : std::string("invalid_edge_") |
186 | | .append(poSrcLayer->GetDescription()); |
187 | | |
188 | | auto poLayer = std::make_unique<GDALVectorCheckCoverageOutputLayer>( |
189 | | *poSrcLayer, geomFieldIndex, layerName, m_maximumGapWidth, |
190 | | m_includeValid); |
191 | | |
192 | | if (!poDstDS->AddProcessedLayer(std::move(poLayer), layerProgressFunc, |
193 | | layerProgressData.get())) |
194 | | { |
195 | | return false; |
196 | | } |
197 | | } |
198 | | |
199 | | m_outputDataset.Set(std::move(poDstDS)); |
200 | | |
201 | | return true; |
202 | | } |
203 | | |
204 | | #else |
205 | | |
206 | | bool GDALVectorCheckCoverageAlgorithm::RunStep(GDALPipelineStepRunContext &) |
207 | 0 | { |
208 | 0 | ReportError(CE_Failure, CPLE_AppDefined, |
209 | 0 | "%s requires GDAL to be built against version 3.12 or later of " |
210 | 0 | "the GEOS library.", |
211 | 0 | NAME); |
212 | 0 | return false; |
213 | 0 | } |
214 | | #endif // HAVE_GEOS |
215 | | |
216 | | GDALVectorCheckCoverageAlgorithmStandalone:: |
217 | 0 | ~GDALVectorCheckCoverageAlgorithmStandalone() = default; |
218 | | |
219 | | //! @endcond |