/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 | | protected: |
103 | | using GDALVectorGeomOneToOneAlgorithmLayer::TranslateFeature; |
104 | | |
105 | | std::unique_ptr<OGRFeature> |
106 | | TranslateFeature(std::unique_ptr<OGRFeature> poSrcFeature) const override; |
107 | | |
108 | | private: |
109 | | CPLStringList m_aosBufferOptions{}; |
110 | | const OGRFeatureDefnRefCountedPtr m_poFeatureDefn; |
111 | | |
112 | | CPL_DISALLOW_COPY_ASSIGN(GDALVectorBufferAlgorithmLayer) |
113 | | }; |
114 | | |
115 | | /************************************************************************/ |
116 | | /* TranslateFeature() */ |
117 | | /************************************************************************/ |
118 | | |
119 | | std::unique_ptr<OGRFeature> GDALVectorBufferAlgorithmLayer::TranslateFeature( |
120 | | std::unique_ptr<OGRFeature> poSrcFeature) const |
121 | | { |
122 | | const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount(); |
123 | | for (int i = 0; i < nGeomFieldCount; ++i) |
124 | | { |
125 | | if (IsSelectedGeomField(i)) |
126 | | { |
127 | | if (auto poGeom = std::unique_ptr<OGRGeometry>( |
128 | | poSrcFeature->StealGeometry(i))) |
129 | | { |
130 | | poGeom.reset(poGeom->BufferEx(m_opts.m_distance, |
131 | | m_aosBufferOptions.List())); |
132 | | if (poGeom) |
133 | | { |
134 | | const auto poGeomFieldDefn = |
135 | | m_poFeatureDefn->GetGeomFieldDefn(i); |
136 | | poGeom = OGRGeometryFactory::forceTo( |
137 | | std::move(poGeom), poGeomFieldDefn->GetType()); |
138 | | if (poGeom) |
139 | | { |
140 | | poGeom->assignSpatialReference( |
141 | | poGeomFieldDefn->GetSpatialRef()); |
142 | | poSrcFeature->SetGeomField(i, std::move(poGeom)); |
143 | | } |
144 | | } |
145 | | } |
146 | | } |
147 | | } |
148 | | |
149 | | poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get()); |
150 | | return poSrcFeature; |
151 | | } |
152 | | |
153 | | } // namespace |
154 | | |
155 | | #endif // HAVE_GEOS |
156 | | |
157 | | /************************************************************************/ |
158 | | /* GDALVectorBufferAlgorithm::CreateAlgLayer() */ |
159 | | /************************************************************************/ |
160 | | |
161 | | std::unique_ptr<OGRLayerWithTranslateFeature> |
162 | | GDALVectorBufferAlgorithm::CreateAlgLayer([[maybe_unused]] OGRLayer &srcLayer) |
163 | 0 | { |
164 | | #ifdef HAVE_GEOS |
165 | | return std::make_unique<GDALVectorBufferAlgorithmLayer>(srcLayer, m_opts); |
166 | | #else |
167 | 0 | CPLAssert(false); |
168 | 0 | return nullptr; |
169 | 0 | #endif |
170 | 0 | } |
171 | | |
172 | | /************************************************************************/ |
173 | | /* GDALVectorBufferAlgorithm::RunStep() */ |
174 | | /************************************************************************/ |
175 | | |
176 | | bool GDALVectorBufferAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt) |
177 | 0 | { |
178 | | #ifdef HAVE_GEOS |
179 | | if (m_opts.m_side == "right") |
180 | | m_opts.m_distance = -m_opts.m_distance; |
181 | | |
182 | | return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt); |
183 | | #else |
184 | 0 | (void)ctxt; |
185 | 0 | ReportError(CE_Failure, CPLE_NotSupported, |
186 | 0 | "This algorithm is only supported for builds against GEOS"); |
187 | 0 | return false; |
188 | 0 | #endif |
189 | 0 | } |
190 | | |
191 | 0 | GDALVectorBufferAlgorithmStandalone::~GDALVectorBufferAlgorithmStandalone() = |
192 | | default; |
193 | | |
194 | | //! @endcond |