/src/gdal/apps/gdalalg_vector_buffer.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GDAL |
4 | | * Purpose: "gdal vector buffer" |
5 | | * Author: Even Rouault <even dot rouault at spatialys.com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdalalg_vector_buffer.h" |
14 | | |
15 | | #include "gdal_priv.h" |
16 | | #include "ogrsf_frmts.h" |
17 | | |
18 | | //! @cond Doxygen_Suppress |
19 | | |
20 | | #ifndef _ |
21 | 0 | #define _(x) (x) |
22 | | #endif |
23 | | |
24 | | /************************************************************************/ |
25 | | /* GDALVectorBufferAlgorithm() */ |
26 | | /************************************************************************/ |
27 | | |
28 | | GDALVectorBufferAlgorithm::GDALVectorBufferAlgorithm(bool standaloneStep) |
29 | 0 | : GDALVectorGeomAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL, |
30 | 0 | standaloneStep, m_opts) |
31 | 0 | { |
32 | 0 | AddArg("distance", 0, _("Distance to which to extend the geometry."), |
33 | 0 | &m_opts.m_distance) |
34 | 0 | .SetPositional() |
35 | 0 | .SetRequired(); |
36 | 0 | AddArg("endcap-style", 0, _("Endcap style."), &m_opts.m_endCapStyle) |
37 | 0 | .SetChoices("round", "flat", "square") |
38 | 0 | .SetDefault(m_opts.m_endCapStyle); |
39 | 0 | AddArg("join-style", 0, _("Join style."), &m_opts.m_joinStyle) |
40 | 0 | .SetChoices("round", "mitre", "bevel") |
41 | 0 | .SetDefault(m_opts.m_joinStyle); |
42 | 0 | AddArg("mitre-limit", 0, |
43 | 0 | _("Mitre ratio limit (only affects mitered join style)."), |
44 | 0 | &m_opts.m_mitreLimit) |
45 | 0 | .SetDefault(m_opts.m_mitreLimit) |
46 | 0 | .SetMinValueIncluded(0); |
47 | 0 | AddArg("quadrant-segments", 0, |
48 | 0 | _("Number of line segments used to approximate a quarter circle."), |
49 | 0 | &m_opts.m_quadrantSegments) |
50 | 0 | .SetDefault(m_opts.m_quadrantSegments) |
51 | 0 | .SetMinValueIncluded(1); |
52 | 0 | AddArg("side", 0, |
53 | 0 | _("Sets whether the computed buffer should be single-sided or not."), |
54 | 0 | &m_opts.m_side) |
55 | 0 | .SetChoices("both", "left", "right") |
56 | 0 | .SetDefault(m_opts.m_side); |
57 | 0 | } |
58 | | |
59 | | #ifdef HAVE_GEOS |
60 | | |
61 | | namespace |
62 | | { |
63 | | |
64 | | /************************************************************************/ |
65 | | /* GDALVectorBufferAlgorithmLayer */ |
66 | | /************************************************************************/ |
67 | | |
68 | | class GDALVectorBufferAlgorithmLayer final |
69 | | : public GDALVectorGeomOneToOneAlgorithmLayer<GDALVectorBufferAlgorithm> |
70 | | { |
71 | | public: |
72 | | GDALVectorBufferAlgorithmLayer( |
73 | | OGRLayer &oSrcLayer, const GDALVectorBufferAlgorithm::Options &opts) |
74 | | : GDALVectorGeomOneToOneAlgorithmLayer<GDALVectorBufferAlgorithm>( |
75 | | oSrcLayer, opts), |
76 | | m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone()) |
77 | | { |
78 | | for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i) |
79 | | { |
80 | | if (IsSelectedGeomField(i)) |
81 | | { |
82 | | m_poFeatureDefn->GetGeomFieldDefn(i)->SetType(wkbMultiPolygon); |
83 | | } |
84 | | } |
85 | | |
86 | | m_aosBufferOptions.SetNameValue("ENDCAP_STYLE", |
87 | | opts.m_endCapStyle.c_str()); |
88 | | m_aosBufferOptions.SetNameValue("JOIN_STYLE", opts.m_joinStyle.c_str()); |
89 | | m_aosBufferOptions.SetNameValue("MITRE_LIMIT", |
90 | | CPLSPrintf("%.17g", opts.m_mitreLimit)); |
91 | | m_aosBufferOptions.SetNameValue( |
92 | | "QUADRANT_SEGMENTS", CPLSPrintf("%d", opts.m_quadrantSegments)); |
93 | | m_aosBufferOptions.SetNameValue("SINGLE_SIDED", |
94 | | m_opts.m_side != "both" ? "YES" : "NO"); |
95 | | } |
96 | | |
97 | | const OGRFeatureDefn *GetLayerDefn() const override |
98 | | { |
99 | | return m_poFeatureDefn.get(); |
100 | | } |
101 | | |
102 | | OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent, |
103 | | bool bForce) override |
104 | | { |
105 | | if (IsSelectedGeomField(iGeomField)) |
106 | | return OGRLayer::IGetExtent(iGeomField, psExtent, bForce); |
107 | | return m_srcLayer.GetExtent(iGeomField, psExtent, bForce); |
108 | | } |
109 | | |
110 | | bool TestCapability(const char *pszCap) const override |
111 | | { |
112 | | if (EQUAL(pszCap, OLCFastGetExtent)) |
113 | | return false; |
114 | | return GDALVectorGeomOneToOneAlgorithmLayer::TestCapability(pszCap); |
115 | | } |
116 | | |
117 | | protected: |
118 | | using GDALVectorGeomOneToOneAlgorithmLayer::TranslateFeature; |
119 | | |
120 | | std::unique_ptr<OGRFeature> |
121 | | TranslateFeature(std::unique_ptr<OGRFeature> poSrcFeature) const override; |
122 | | |
123 | | private: |
124 | | CPLStringList m_aosBufferOptions{}; |
125 | | const OGRFeatureDefnRefCountedPtr m_poFeatureDefn; |
126 | | |
127 | | CPL_DISALLOW_COPY_ASSIGN(GDALVectorBufferAlgorithmLayer) |
128 | | }; |
129 | | |
130 | | /************************************************************************/ |
131 | | /* TranslateFeature() */ |
132 | | /************************************************************************/ |
133 | | |
134 | | std::unique_ptr<OGRFeature> GDALVectorBufferAlgorithmLayer::TranslateFeature( |
135 | | std::unique_ptr<OGRFeature> poSrcFeature) const |
136 | | { |
137 | | const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount(); |
138 | | for (int i = 0; i < nGeomFieldCount; ++i) |
139 | | { |
140 | | if (IsSelectedGeomField(i)) |
141 | | { |
142 | | if (auto poGeom = std::unique_ptr<OGRGeometry>( |
143 | | poSrcFeature->StealGeometry(i))) |
144 | | { |
145 | | poGeom.reset(poGeom->BufferEx(m_opts.m_distance, |
146 | | m_aosBufferOptions.List())); |
147 | | if (poGeom) |
148 | | { |
149 | | const auto poGeomFieldDefn = |
150 | | m_poFeatureDefn->GetGeomFieldDefn(i); |
151 | | poGeom = OGRGeometryFactory::forceTo( |
152 | | std::move(poGeom), poGeomFieldDefn->GetType()); |
153 | | if (poGeom) |
154 | | { |
155 | | poGeom->assignSpatialReference( |
156 | | poGeomFieldDefn->GetSpatialRef()); |
157 | | poSrcFeature->SetGeomField(i, std::move(poGeom)); |
158 | | } |
159 | | } |
160 | | } |
161 | | } |
162 | | } |
163 | | |
164 | | poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get()); |
165 | | return poSrcFeature; |
166 | | } |
167 | | |
168 | | } // namespace |
169 | | |
170 | | #endif // HAVE_GEOS |
171 | | |
172 | | /************************************************************************/ |
173 | | /* GDALVectorBufferAlgorithm::CreateAlgLayer() */ |
174 | | /************************************************************************/ |
175 | | |
176 | | std::unique_ptr<OGRLayerWithTranslateFeature> |
177 | | GDALVectorBufferAlgorithm::CreateAlgLayer([[maybe_unused]] OGRLayer &srcLayer) |
178 | 0 | { |
179 | | #ifdef HAVE_GEOS |
180 | | return std::make_unique<GDALVectorBufferAlgorithmLayer>(srcLayer, m_opts); |
181 | | #else |
182 | 0 | CPLAssert(false); |
183 | 0 | return nullptr; |
184 | 0 | #endif |
185 | 0 | } |
186 | | |
187 | | /************************************************************************/ |
188 | | /* GDALVectorBufferAlgorithm::RunStep() */ |
189 | | /************************************************************************/ |
190 | | |
191 | | bool GDALVectorBufferAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
192 | 0 | { |
193 | | #ifdef HAVE_GEOS |
194 | | if (m_opts.m_side == "right") |
195 | | m_opts.m_distance = -m_opts.m_distance; |
196 | | |
197 | | return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt); |
198 | | #else |
199 | 0 | (void)ctxt; |
200 | 0 | ReportError(CE_Failure, CPLE_NotSupported, |
201 | 0 | "This algorithm is only supported for builds against GEOS"); |
202 | 0 | return false; |
203 | 0 | #endif |
204 | 0 | } |
205 | | |
206 | 0 | GDALVectorBufferAlgorithmStandalone::~GDALVectorBufferAlgorithmStandalone() = |
207 | | default; |
208 | | |
209 | | //! @endcond |