/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 | | m_poFeatureDefn->Reference(); |
79 | | for (int i = 0; i < m_poFeatureDefn->GetGeomFieldCount(); ++i) |
80 | | { |
81 | | if (IsSelectedGeomField(i)) |
82 | | { |
83 | | m_poFeatureDefn->GetGeomFieldDefn(i)->SetType(wkbMultiPolygon); |
84 | | } |
85 | | } |
86 | | |
87 | | m_aosBufferOptions.SetNameValue("ENDCAP_STYLE", |
88 | | opts.m_endCapStyle.c_str()); |
89 | | m_aosBufferOptions.SetNameValue("JOIN_STYLE", opts.m_joinStyle.c_str()); |
90 | | m_aosBufferOptions.SetNameValue("MITRE_LIMIT", |
91 | | CPLSPrintf("%.17g", opts.m_mitreLimit)); |
92 | | m_aosBufferOptions.SetNameValue( |
93 | | "QUADRANT_SEGMENTS", CPLSPrintf("%d", opts.m_quadrantSegments)); |
94 | | m_aosBufferOptions.SetNameValue("SINGLE_SIDED", |
95 | | m_opts.m_side != "both" ? "YES" : "NO"); |
96 | | } |
97 | | |
98 | | ~GDALVectorBufferAlgorithmLayer() override |
99 | | { |
100 | | m_poFeatureDefn->Release(); |
101 | | } |
102 | | |
103 | | const OGRFeatureDefn *GetLayerDefn() const override |
104 | | { |
105 | | return m_poFeatureDefn; |
106 | | } |
107 | | |
108 | | protected: |
109 | | using GDALVectorGeomOneToOneAlgorithmLayer::TranslateFeature; |
110 | | |
111 | | std::unique_ptr<OGRFeature> |
112 | | TranslateFeature(std::unique_ptr<OGRFeature> poSrcFeature) const override; |
113 | | |
114 | | private: |
115 | | CPLStringList m_aosBufferOptions{}; |
116 | | OGRFeatureDefn *const m_poFeatureDefn; |
117 | | |
118 | | CPL_DISALLOW_COPY_ASSIGN(GDALVectorBufferAlgorithmLayer) |
119 | | }; |
120 | | |
121 | | /************************************************************************/ |
122 | | /* TranslateFeature() */ |
123 | | /************************************************************************/ |
124 | | |
125 | | std::unique_ptr<OGRFeature> GDALVectorBufferAlgorithmLayer::TranslateFeature( |
126 | | std::unique_ptr<OGRFeature> poSrcFeature) const |
127 | | { |
128 | | const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount(); |
129 | | for (int i = 0; i < nGeomFieldCount; ++i) |
130 | | { |
131 | | if (IsSelectedGeomField(i)) |
132 | | { |
133 | | if (auto poGeom = std::unique_ptr<OGRGeometry>( |
134 | | poSrcFeature->StealGeometry(i))) |
135 | | { |
136 | | poGeom.reset(poGeom->BufferEx(m_opts.m_distance, |
137 | | m_aosBufferOptions.List())); |
138 | | if (poGeom) |
139 | | { |
140 | | const auto poGeomFieldDefn = |
141 | | m_poFeatureDefn->GetGeomFieldDefn(i); |
142 | | poGeom = OGRGeometryFactory::forceTo( |
143 | | std::move(poGeom), poGeomFieldDefn->GetType()); |
144 | | if (poGeom) |
145 | | { |
146 | | poGeom->assignSpatialReference( |
147 | | poGeomFieldDefn->GetSpatialRef()); |
148 | | poSrcFeature->SetGeomField(i, std::move(poGeom)); |
149 | | } |
150 | | } |
151 | | } |
152 | | } |
153 | | } |
154 | | |
155 | | poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn); |
156 | | return poSrcFeature; |
157 | | } |
158 | | |
159 | | } // namespace |
160 | | |
161 | | #endif // HAVE_GEOS |
162 | | |
163 | | /************************************************************************/ |
164 | | /* GDALVectorBufferAlgorithm::CreateAlgLayer() */ |
165 | | /************************************************************************/ |
166 | | |
167 | | std::unique_ptr<OGRLayerWithTranslateFeature> |
168 | | GDALVectorBufferAlgorithm::CreateAlgLayer([[maybe_unused]] OGRLayer &srcLayer) |
169 | 0 | { |
170 | | #ifdef HAVE_GEOS |
171 | | return std::make_unique<GDALVectorBufferAlgorithmLayer>(srcLayer, m_opts); |
172 | | #else |
173 | 0 | CPLAssert(false); |
174 | 0 | return nullptr; |
175 | 0 | #endif |
176 | 0 | } |
177 | | |
178 | | /************************************************************************/ |
179 | | /* GDALVectorBufferAlgorithm::RunStep() */ |
180 | | /************************************************************************/ |
181 | | |
182 | | bool GDALVectorBufferAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
183 | 0 | { |
184 | | #ifdef HAVE_GEOS |
185 | | if (m_opts.m_side == "right") |
186 | | m_opts.m_distance = -m_opts.m_distance; |
187 | | |
188 | | return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt); |
189 | | #else |
190 | 0 | (void)ctxt; |
191 | 0 | ReportError(CE_Failure, CPLE_NotSupported, |
192 | 0 | "This algorithm is only supported for builds against GEOS"); |
193 | 0 | return false; |
194 | 0 | #endif |
195 | 0 | } |
196 | | |
197 | 0 | GDALVectorBufferAlgorithmStandalone::~GDALVectorBufferAlgorithmStandalone() = |
198 | | default; |
199 | | |
200 | | //! @endcond |