Skip to content

Commit 2b5fe2e

Browse files
committed
CLI: Add gdal vector dissolve
1 parent 5d1fb11 commit 2b5fe2e

File tree

9 files changed

+305
-0
lines changed

9 files changed

+305
-0
lines changed

apps/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ target_sources(appslib PRIVATE
103103
gdalalg_vector_collect.cpp
104104
gdalalg_vector_concat.cpp
105105
gdalalg_vector_convert.cpp
106+
gdalalg_vector_dissolve.cpp
106107
gdalalg_vector_edit.cpp
107108
gdalalg_vector_pipeline.cpp
108109
gdalalg_vector_rasterize.cpp

apps/gdalalg_vector.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "gdalalg_vector_collect.h"
2222
#include "gdalalg_vector_concat.h"
2323
#include "gdalalg_vector_convert.h"
24+
#include "gdalalg_vector_dissolve.h"
2425
#include "gdalalg_vector_edit.h"
2526
#include "gdalalg_vector_explode_collections.h"
2627
#include "gdalalg_vector_geom.h"
@@ -77,6 +78,7 @@ class GDALVectorAlgorithm final : public GDALAlgorithm
7778
RegisterSubAlgorithm<GDALVectorCollectAlgorithmStandalone>();
7879
RegisterSubAlgorithm<GDALVectorConcatAlgorithmStandalone>();
7980
RegisterSubAlgorithm<GDALVectorConvertAlgorithm>();
81+
RegisterSubAlgorithm<GDALVectorDissolveAlgorithmStandalone>();
8082
RegisterSubAlgorithm<GDALVectorEditAlgorithmStandalone>();
8183
RegisterSubAlgorithm<GDALVectorExplodeCollectionsAlgorithmStandalone>();
8284
RegisterSubAlgorithm<GDALVectorGridAlgorithmStandalone>();

apps/gdalalg_vector_dissolve.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/******************************************************************************
2+
*
3+
* Project: GDAL
4+
* Purpose: "gdal vector dissolve"
5+
* Author: Dan Baston
6+
*
7+
******************************************************************************
8+
* Copyright (c) 2025, ISciences LLC
9+
*
10+
* SPDX-License-Identifier: MIT
11+
****************************************************************************/
12+
13+
#include "gdalalg_vector_dissolve.h"
14+
15+
#include "gdal_priv.h"
16+
#include "ogrsf_frmts.h"
17+
18+
//! @cond Doxygen_Suppress
19+
20+
#ifndef _
21+
#define _(x) (x)
22+
#endif
23+
24+
/************************************************************************/
25+
/* GDALVectorDissolveAlgorithm() */
26+
/************************************************************************/
27+
28+
GDALVectorDissolveAlgorithm::GDALVectorDissolveAlgorithm(bool standaloneStep)
29+
: GDALVectorGeomAbstractAlgorithm(NAME, DESCRIPTION, HELP_URL,
30+
standaloneStep, m_opts)
31+
{
32+
}
33+
34+
#ifdef HAVE_GEOS
35+
36+
namespace
37+
{
38+
39+
/************************************************************************/
40+
/* GDALVectorDissolveAlgorithmLayer */
41+
/************************************************************************/
42+
43+
class GDALVectorDissolveAlgorithmLayer final
44+
: public GDALVectorGeomOneToOneAlgorithmLayer<GDALVectorDissolveAlgorithm>
45+
{
46+
public:
47+
GDALVectorDissolveAlgorithmLayer(
48+
OGRLayer &oSrcLayer, const GDALVectorDissolveAlgorithm::Options &opts)
49+
: GDALVectorGeomOneToOneAlgorithmLayer<GDALVectorDissolveAlgorithm>(
50+
oSrcLayer, opts)
51+
{
52+
}
53+
54+
protected:
55+
using GDALVectorGeomOneToOneAlgorithmLayer::TranslateFeature;
56+
57+
std::unique_ptr<OGRFeature>
58+
TranslateFeature(std::unique_ptr<OGRFeature> poSrcFeature) const override;
59+
60+
private:
61+
};
62+
63+
/************************************************************************/
64+
/* TranslateFeature() */
65+
/************************************************************************/
66+
67+
std::unique_ptr<OGRFeature> GDALVectorDissolveAlgorithmLayer::TranslateFeature(
68+
std::unique_ptr<OGRFeature> poSrcFeature) const
69+
{
70+
const int nGeomFieldCount = poSrcFeature->GetGeomFieldCount();
71+
for (int i = 0; i < nGeomFieldCount; ++i)
72+
{
73+
if (IsSelectedGeomField(i))
74+
{
75+
if (auto poGeom = std::unique_ptr<OGRGeometry>(
76+
poSrcFeature->StealGeometry(i)))
77+
{
78+
poGeom.reset(poGeom->UnaryUnion());
79+
80+
if (poGeom)
81+
{
82+
poGeom->assignSpatialReference(m_srcLayer.GetLayerDefn()
83+
->GetGeomFieldDefn(i)
84+
->GetSpatialRef());
85+
poSrcFeature->SetGeomField(i, std::move(poGeom));
86+
}
87+
}
88+
}
89+
}
90+
91+
return poSrcFeature;
92+
}
93+
94+
} // namespace
95+
96+
#endif // HAVE_GEOS
97+
98+
/************************************************************************/
99+
/* GDALVectorDissolveAlgorithm::CreateAlgLayer() */
100+
/************************************************************************/
101+
102+
std::unique_ptr<OGRLayerWithTranslateFeature>
103+
GDALVectorDissolveAlgorithm::CreateAlgLayer([[maybe_unused]] OGRLayer &srcLayer)
104+
{
105+
#ifdef HAVE_GEOS
106+
return std::make_unique<GDALVectorDissolveAlgorithmLayer>(srcLayer, m_opts);
107+
#else
108+
CPLAssert(false);
109+
return nullptr;
110+
#endif
111+
}
112+
113+
/************************************************************************/
114+
/* GDALVectorDissolveAlgorithm::RunStep() */
115+
/************************************************************************/
116+
117+
bool GDALVectorDissolveAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
118+
{
119+
#ifdef HAVE_GEOS
120+
return GDALVectorGeomAbstractAlgorithm::RunStep(ctxt);
121+
#else
122+
(void)ctxt;
123+
ReportError(CE_Failure, CPLE_NotSupported,
124+
"This algorithm is only supported for builds against GEOS");
125+
return false;
126+
#endif
127+
}
128+
129+
GDALVectorDissolveAlgorithmStandalone::
130+
~GDALVectorDissolveAlgorithmStandalone() = default;
131+
132+
//! @endcond

apps/gdalalg_vector_dissolve.h

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/******************************************************************************
2+
*
3+
* Project: GDAL
4+
* Purpose: "gdal vector dissolve"
5+
* Author: Daniel Baston
6+
*
7+
******************************************************************************
8+
* Copyright (c) 2025, ISciences LLC
9+
*
10+
* SPDX-License-Identifier: MIT
11+
****************************************************************************/
12+
13+
#ifndef GDALALG_VECTOR_DISSOLVE_INCLUDED
14+
#define GDALALG_VECTOR_DISSOLVE_INCLUDED
15+
16+
#include "gdalalg_vector_geom.h"
17+
#include "cpl_progress.h"
18+
19+
#include <string>
20+
21+
//! @cond Doxygen_Suppress
22+
23+
/************************************************************************/
24+
/* GDALVectorDissolveAlgorithm */
25+
/************************************************************************/
26+
27+
class GDALVectorDissolveAlgorithm : public GDALVectorGeomAbstractAlgorithm
28+
{
29+
public:
30+
static constexpr const char *NAME = "dissolve";
31+
static constexpr const char *DESCRIPTION = "Dissolves multipart features";
32+
static constexpr const char *HELP_URL =
33+
"/programs/gdal_vector_dissolve.html";
34+
35+
explicit GDALVectorDissolveAlgorithm(bool standaloneStep = false);
36+
37+
std::unique_ptr<OGRLayerWithTranslateFeature>
38+
CreateAlgLayer(OGRLayer &srcLayer) override;
39+
40+
struct Options : OptionsBase
41+
{
42+
};
43+
44+
private:
45+
bool RunStep(GDALPipelineStepRunContext &ctxt) override;
46+
47+
Options m_opts{};
48+
};
49+
50+
/************************************************************************/
51+
/* GDALVectorDissolveAlgorithmStandalone */
52+
/************************************************************************/
53+
54+
class GDALVectorDissolveAlgorithmStandalone final
55+
: public GDALVectorDissolveAlgorithm
56+
{
57+
public:
58+
GDALVectorDissolveAlgorithmStandalone()
59+
: GDALVectorDissolveAlgorithm(/* standaloneStep = */ true)
60+
{
61+
}
62+
63+
~GDALVectorDissolveAlgorithmStandalone() override;
64+
};
65+
66+
//! @endcond
67+
68+
#endif /* GDALALG_VECTOR_DISSOLVE_INCLUDED */

apps/gdalalg_vector_pipeline.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "gdalalg_vector_clip.h"
2121
#include "gdalalg_vector_collect.h"
2222
#include "gdalalg_vector_concat.h"
23+
#include "gdalalg_vector_dissolve.h"
2324
#include "gdalalg_vector_edit.h"
2425
#include "gdalalg_vector_explode_collections.h"
2526
#include "gdalalg_vector_filter.h"
@@ -154,6 +155,7 @@ void GDALVectorPipelineAlgorithm::RegisterAlgorithms(
154155

155156
registry.Register<GDALVectorClipAlgorithm>(
156157
addSuffixIfNeeded(GDALVectorClipAlgorithm::NAME));
158+
registry.Register<GDALVectorDissolveAlgorithm>();
157159

158160
registry.Register<GDALVectorEditAlgorithm>(
159161
addSuffixIfNeeded(GDALVectorEditAlgorithm::NAME));
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env pytest
2+
# -*- coding: utf-8 -*-
3+
###############################################################################
4+
# Project: GDAL/OGR Test Suite
5+
# Purpose: 'gdal vector dissolve' testing
6+
# Author: Daniel Baston
7+
#
8+
###############################################################################
9+
# Copyright (c) 2025, ISciences LLC
10+
#
11+
# SPDX-License-Identifier: MIT
12+
###############################################################################
13+
14+
import gdaltest
15+
import pytest
16+
17+
from osgeo import gdal, ogr
18+
19+
20+
@pytest.fixture()
21+
def alg():
22+
return gdal.GetGlobalAlgorithmRegistry()["vector"]["dissolve"]
23+
24+
25+
pytestmark = pytest.mark.require_geos
26+
27+
28+
@pytest.mark.parametrize(
29+
"wkt_in,wkt_out",
30+
[
31+
["MULTIPOINT (3 3, 4 4, 3 3)", "MULTIPOINT (3 3, 4 4)"],
32+
[
33+
"MULTIPOLYGON (((0 0, 1 0, 1 1, 0 0)), ((0 0, 1 1, 0 1, 0 0)))",
34+
"POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))",
35+
],
36+
[
37+
"MULTILINESTRING ((0 0, 1 1), (1 1, 2 2), (1 0, 0 1))",
38+
"MULTILINESTRING ((0 0, 0.5 0.5), (0.5 0.5, 1 1), (1 1, 2 2), (1 0, 0.5 0.5), (0.5 0.5, 0 1))",
39+
],
40+
],
41+
)
42+
def test_gdalalg_vector_dissolve(alg, wkt_in, wkt_out):
43+
44+
if type(wkt_in) is str:
45+
wkt_in = [wkt_in]
46+
if type(wkt_out) is str:
47+
wkt_out = [wkt_out]
48+
49+
alg["input"] = gdaltest.wkt_ds(wkt_in)
50+
alg["output"] = ""
51+
alg["output-format"] = "stream"
52+
53+
assert alg.Run()
54+
55+
dst_ds = alg["output"].GetDataset()
56+
dst_lyr = dst_ds.GetLayer(0)
57+
58+
assert dst_lyr.GetFeatureCount() == len(wkt_out)
59+
60+
for f, expected_wkt in zip(dst_lyr, wkt_out):
61+
g1 = f.GetGeometryRef().Normalize()
62+
g2 = ogr.CreateGeometryFromWkt(expected_wkt).Normalize()
63+
assert g1.Equals(g2)

doc/source/conf.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,13 @@ def check_python_bindings():
890890
[author_evenr],
891891
1,
892892
),
893+
(
894+
"programs/gdal_vector_dissolve",
895+
"gdal-vector-dissolve",
896+
"Unions the elmeents of each feature's geometry.",
897+
[author_dbaston],
898+
1,
899+
),
893900
(
894901
"programs/gdal_vector_edit",
895902
"gdal-vector-edit",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.. _gdal_vector_dissolve:
2+
3+
================================================================================
4+
``gdal vector dissolve``
5+
================================================================================
6+
7+
.. versionadded:: 3.13
8+
9+
.. only:: html
10+
11+
Unions the elements of each feature's geometry.
12+
13+
.. Index:: gdal vector dissolve
14+
15+
Synopsis
16+
--------
17+
18+
.. program-output:: gdal vector dissolve --help-doc
19+
20+
Description
21+
-----------
22+
23+
:program:`gdal vector dissolve` performs a union operation on the elements of each feature's geometry. This has the following effects:
24+
25+
- Duplicate vertices are eliminated.
26+
- Nodes are added where input linework intersects.
27+
- Polygons that overlap are "dissolved" into a single feature.
28+
29+
``dissolve`` can be used as a step of :ref:`gdal_vector_pipeline`.

doc/source/programs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ Vector commands
201201
gdal_vector_collect
202202
gdal_vector_concat
203203
gdal_vector_convert
204+
gdal_vector_dissolve
204205
gdal_vector_edit
205206
gdal_vector_filter
206207
gdal_vector_info

0 commit comments

Comments
 (0)