Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 133 additions & 1 deletion apps/gdalalg_vector_clip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,55 @@ class GDALVectorClipAlgorithmLayer final : public GDALVectorPipelineOutputLayer

} // namespace

/************************************************************************/
/* GDALVectorClipAlgorithmLayerChangeExtent */
/************************************************************************/

namespace
{
class GDALVectorClipAlgorithmLayerChangeExtent final
: public GDALVectorPipelinePassthroughLayer
{
public:
GDALVectorClipAlgorithmLayerChangeExtent(OGRLayer &oSrcLayer,
const OGREnvelope &sLayerEnvelope)
: GDALVectorPipelinePassthroughLayer(oSrcLayer),
m_sLayerEnvelope(sLayerEnvelope)
{
}

OGRErr IGetExtent(int /*iGeomField*/, OGREnvelope *psExtent,
bool /* bForce */) override
{
if (m_sLayerEnvelope.IsInit())
{
*psExtent = m_sLayerEnvelope;
return OGRERR_NONE;
}
else
{
return OGRERR_FAILURE;
}
}

int TestCapability(const char *pszCap) const override
{
if (EQUAL(pszCap, OLCFastGetExtent))
return true;
return m_srcLayer.TestCapability(pszCap);
}

private:
const OGREnvelope m_sLayerEnvelope;
};

} // namespace

/************************************************************************/
/* GDALVectorClipAlgorithm::RunStep() */
/************************************************************************/

bool GDALVectorClipAlgorithm::RunStep(GDALPipelineStepRunContext &)
bool GDALVectorClipAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
{
auto poSrcDS = m_inputDataset[0].GetDatasetRef();
CPLAssert(poSrcDS);
Expand All @@ -208,6 +252,94 @@ bool GDALVectorClipAlgorithm::RunStep(GDALPipelineStepRunContext &)
}
}

if (m_bbox.empty() && m_geometry.empty() &&
m_likeDataset.GetDatasetRef() == nullptr)
{
auto outDS =
std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);

bool ret = true;
int64_t nTotalFeatures = 0;
if (ctxt.m_pfnProgress)
{
for (int i = 0; ret && i < nLayerCount; ++i)
{
auto poSrcLayer = poSrcDS->GetLayer(i);
ret = (poSrcLayer != nullptr);
if (ret)
{
if (m_activeLayer.empty() ||
m_activeLayer == poSrcLayer->GetDescription())
{
if (poSrcLayer->TestCapability(OLCFastFeatureCount))
{
const auto nFC = poSrcLayer->GetFeatureCount(false);
if (nFC < 0)
{
nTotalFeatures = 0;
break;
}
nTotalFeatures += nFC;
}
}
}
}
}

int64_t nFeatureCounter = 0;
for (int i = 0; ret && i < nLayerCount; ++i)
{
auto poSrcLayer = poSrcDS->GetLayer(i);
ret = (poSrcLayer != nullptr);
if (ret)
{
if (m_activeLayer.empty() ||
m_activeLayer == poSrcLayer->GetDescription())
{
OGREnvelope sLayerEnvelope, sFeatureEnvelope;
for (auto &&poFeature : poSrcLayer)
{
const auto poGeom = poFeature->GetGeometryRef();
if (poGeom && !poGeom->IsEmpty())
{
poGeom->getEnvelope(&sFeatureEnvelope);
sLayerEnvelope.Merge(sFeatureEnvelope);
}

++nFeatureCounter;
if (nTotalFeatures > 0 && ctxt.m_pfnProgress &&
!ctxt.m_pfnProgress(
static_cast<double>(nFeatureCounter) /
static_cast<double>(nTotalFeatures),
"", ctxt.m_pProgressData))
{
ReportError(CE_Failure, CPLE_UserInterrupt,
"Interrupted by user");
return false;
}
}
outDS->AddLayer(
*poSrcLayer,
std::make_unique<
GDALVectorClipAlgorithmLayerChangeExtent>(
*poSrcLayer, sLayerEnvelope));
}
else
{
outDS->AddLayer(
*poSrcLayer,
std::make_unique<GDALVectorPipelinePassthroughLayer>(
*poSrcLayer));
}
}
}

if (ret)
m_outputDataset.Set(std::move(outDS));

return ret;
}

auto [poClipGeom, errMsg] = GetClipGeometry();
if (!poClipGeom)
{
Expand Down
6 changes: 6 additions & 0 deletions apps/gdalalg_vector_clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ class GDALVectorClipAlgorithm /* non final */

explicit GDALVectorClipAlgorithm(bool standaloneStep = false);

bool IsNativelyStreamingCompatible() const override
{
return !m_bbox.empty() || !m_geometry.empty() ||
!m_likeDataset.GetDatasetRef();
}

private:
bool RunStep(GDALPipelineStepRunContext &ctxt) override;

Expand Down
2 changes: 2 additions & 0 deletions apps/gdalalg_vector_pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ class GDALVectorPipelinePassthroughLayer /* non final */
explicit GDALVectorPipelinePassthroughLayer(OGRLayer &oSrcLayer)
: GDALVectorPipelineOutputLayer(oSrcLayer)
{
SetDescription(oSrcLayer.GetDescription());
SetMetadata(oSrcLayer.GetMetadata());
}

const OGRFeatureDefn *GetLayerDefn() const override;
Expand Down
47 changes: 39 additions & 8 deletions autotest/utilities/test_gdalalg_vector_clip.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,15 +886,46 @@ def test_gdalalg_vector_clip_like_raster_srs():
assert out_lyr.GetNextFeature() is None


def test_gdalalg_vector_clip_missing_arg(tmp_vsimem):

out_filename = str(tmp_vsimem / "out.shp")
@pytest.mark.require_driver("GPKG")
def test_gdalalg_vector_clip_no_arg(tmp_vsimem):

clip = get_clip_alg()
with pytest.raises(
Exception, match="clip: --bbox, --geometry or --like must be specified"
):
clip.ParseRunAndFinalize(["../ogr/data/poly.shp", out_filename])
src_ds = gdal.GetDriverByName("GPKG").CreateVector(tmp_vsimem / "in.gpkg")
src_lyr = src_ds.CreateLayer("test")
f = ogr.Feature(src_lyr.GetLayerDefn())
f.SetGeometry(ogr.CreateGeometryFromWkt("POLYGON ((0 0,0 1,1 1,1 0,0 0))"))
src_lyr.CreateFeature(f)
f = ogr.Feature(src_lyr.GetLayerDefn())
f.SetGeometry(ogr.CreateGeometryFromWkt("POLYGON ((10 0,10 1,11 1,11 0,10 0))"))
src_lyr.CreateFeature(f)
src_lyr.DeleteFeature(f.GetFID())
assert src_lyr.GetExtent() == (0, 11, 0, 1)
src_ds.CreateLayer("empty_layer")

with gdal.alg.vector.clip(input=src_ds, output="", output_format="MEM") as alg:
ds = alg.Output()
lyr = ds.GetLayerByName("test")
assert lyr.GetExtent() == (0, 1, 0, 1)
assert lyr.GetFeatureCount() == 1
lyr = ds.GetLayerByName("empty_layer")
assert lyr.GetExtent(can_return_null=True) is None

tab_pct = [0]

def my_progress(pct, msg, user_data):
assert pct >= tab_pct[0]
tab_pct[0] = pct
return True

with gdal.alg.vector.clip(
input=src_ds, output="", output_format="MEM", progress=my_progress
) as alg:
assert tab_pct[0] == 1.0
ds = alg.Output()
lyr = ds.GetLayerByName("test")
assert lyr.GetExtent() == (0, 1, 0, 1)
assert lyr.GetFeatureCount() == 1
lyr = ds.GetLayerByName("empty_layer")
assert lyr.GetExtent(can_return_null=True) is None


def test_gdalalg_vector_clip_geometry_invalid():
Expand Down
4 changes: 3 additions & 1 deletion doc/source/programs/gdal_vector_clip.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ Description
:program:`gdal vector clip` can be used to clip a vector dataset using
georeferenced coordinates.

Either :option:`--bbox`, :option:`--geometry` or :option:`--like` must be specified.
Starting with GDAL 3.13, if none of :option:`--bbox`, :option:`--geometry` or :option:`--like`
is specified, the extent of output layer(s) is adjusted to fit exactly the
actual extent of input layer(s).

``clip`` can also be used as a step of :ref:`gdal_vector_pipeline`.

Expand Down
11 changes: 11 additions & 0 deletions doc/source/programs/gdal_vector_rasterize.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,14 @@ Examples
.. code-block:: bash

gdal vector rasterize --burn 255,0,0 --ot Byte --size 1000,1000 -l footprints footprints.shp mask.tif

.. example::
:title: Burn a shapefile into a raster using a specific where condition to select features, and clip the extent to the one of selected features

.. code-block:: bash

gdal pipeline read /vsizip/vsicurl/https://www2.census.gov/geo/tiger/TIGER2025/STATE/tl_2025_us_state.zip ! \
filter --where "stusps = 'CA'" ! \
clip ! \
rasterize --burn 1 --size 1500,1500 --datatype Byte ! \
write out.png --overwrite
Loading