diff --git a/apps/gdalalg_vector_filter.cpp b/apps/gdalalg_vector_filter.cpp index 50adcc6ca489..56b9e18087ab 100644 --- a/apps/gdalalg_vector_filter.cpp +++ b/apps/gdalalg_vector_filter.cpp @@ -14,6 +14,9 @@ #include "gdal_priv.h" #include "ogrsf_frmts.h" +#include "ogr_p.h" + +#include //! @cond Doxygen_Suppress @@ -30,8 +33,239 @@ GDALVectorFilterAlgorithm::GDALVectorFilterAlgorithm(bool standaloneStep) standaloneStep) { AddBBOXArg(&m_bbox); + AddArg("where", 0, + _("Attribute query in a restricted form of the queries used in the " + "SQL WHERE statement"), + &m_where) + .SetReadFromFileAtSyntaxAllowed() + .SetMetaVar("|@") + .SetRemoveSQLCommentsEnabled(); + AddArg("fields", 0, _("Selected fields"), &m_selectedFields); + AddStrictLaxArg(); } +namespace +{ + +/************************************************************************/ +/* GDALVectorFilterAlgorithmDataset */ +/************************************************************************/ + +class GDALVectorFilterAlgorithmDataset final : public GDALDataset +{ + std::vector> m_layers{}; + + public: + GDALVectorFilterAlgorithmDataset() = default; + + void AddLayer(std::unique_ptr poLayer) + { + m_layers.push_back(std::move(poLayer)); + } + + int GetLayerCount() override + { + return static_cast(m_layers.size()); + } + + OGRLayer *GetLayer(int idx) override + { + return idx >= 0 && idx < GetLayerCount() ? m_layers[idx].get() + : nullptr; + } +}; + +/************************************************************************/ +/* GDALVectorFilterAlgorithmLayer */ +/************************************************************************/ + +class GDALVectorFilterAlgorithmLayer final : public OGRLayer +{ + private: + bool m_bIsOK = true; + OGRLayer *const m_poSrcLayer; + OGRFeatureDefn *const m_poFeatureDefn = nullptr; + std::vector m_anMapSrcFieldsToDstFields{}; + std::vector m_anMapDstGeomFieldsToSrcGeomFields{}; + + CPL_DISALLOW_COPY_ASSIGN(GDALVectorFilterAlgorithmLayer) + + std::unique_ptr TranslateFeature(OGRFeature *poSrcFeature) const + { + auto poFeature = std::make_unique(m_poFeatureDefn); + poFeature->SetFID(poSrcFeature->GetFID()); + const auto styleString = poSrcFeature->GetStyleString(); + if (styleString) + poFeature->SetStyleString(styleString); + poFeature->SetFieldsFrom( + poSrcFeature, m_anMapSrcFieldsToDstFields.data(), false, false); + int iDstGeomField = 0; + for (int nSrcGeomField : m_anMapDstGeomFieldsToSrcGeomFields) + { + poFeature->SetGeomFieldDirectly( + iDstGeomField, poSrcFeature->StealGeometry(nSrcGeomField)); + ++iDstGeomField; + } + return poFeature; + } + + public: + GDALVectorFilterAlgorithmLayer( + OGRLayer *poSrcLayer, const std::vector &selectedFields, + bool bStrict) + : m_poSrcLayer(poSrcLayer), + m_poFeatureDefn(new OGRFeatureDefn(poSrcLayer->GetName())) + { + SetDescription(poSrcLayer->GetDescription()); + m_poFeatureDefn->SetGeomType(wkbNone); + m_poFeatureDefn->Reference(); + + std::set oSetSelFields; + std::set oSetSelFieldsUC; + for (const std::string &osFieldName : selectedFields) + { + oSetSelFields.insert(osFieldName); + oSetSelFieldsUC.insert(CPLString(osFieldName).toupper()); + } + + std::set oSetUsedSetFieldsUC; + + const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn(); + for (int i = 0; i < poSrcLayerDefn->GetFieldCount(); ++i) + { + const auto poSrcFieldDefn = poSrcLayerDefn->GetFieldDefn(i); + auto oIter = oSetSelFieldsUC.find( + CPLString(poSrcFieldDefn->GetNameRef()).toupper()); + if (oIter != oSetSelFieldsUC.end()) + { + m_anMapSrcFieldsToDstFields.push_back( + m_poFeatureDefn->GetFieldCount()); + OGRFieldDefn oDstFieldDefn(*poSrcFieldDefn); + m_poFeatureDefn->AddFieldDefn(&oDstFieldDefn); + oSetUsedSetFieldsUC.insert(*oIter); + } + else + { + m_anMapSrcFieldsToDstFields.push_back(-1); + } + } + + for (int i = 0; i < poSrcLayerDefn->GetGeomFieldCount(); ++i) + { + const auto poSrcFieldDefn = poSrcLayerDefn->GetGeomFieldDefn(i); + auto oIter = oSetSelFieldsUC.find( + CPLString(poSrcFieldDefn->GetNameRef()).toupper()); + if (oIter != oSetSelFieldsUC.end()) + { + m_anMapDstGeomFieldsToSrcGeomFields.push_back(i); + OGRGeomFieldDefn oDstFieldDefn(*poSrcFieldDefn); + m_poFeatureDefn->AddGeomFieldDefn(&oDstFieldDefn); + oSetUsedSetFieldsUC.insert(*oIter); + } + } + + auto oIter = oSetSelFieldsUC.find( + CPLString(OGR_GEOMETRY_DEFAULT_NON_EMPTY_NAME).toupper()); + if (m_poFeatureDefn->GetGeomFieldCount() == 0 && + oIter != oSetSelFieldsUC.end() && + poSrcLayerDefn->GetGeomFieldCount() == 1) + { + const auto poSrcFieldDefn = poSrcLayerDefn->GetGeomFieldDefn(0); + m_anMapDstGeomFieldsToSrcGeomFields.push_back(0); + OGRGeomFieldDefn oDstFieldDefn(*poSrcFieldDefn); + m_poFeatureDefn->AddGeomFieldDefn(&oDstFieldDefn); + oSetUsedSetFieldsUC.insert(*oIter); + } + + if (oSetUsedSetFieldsUC.size() != oSetSelFields.size()) + { + for (const std::string &osName : oSetSelFields) + { + if (!cpl::contains(oSetUsedSetFieldsUC, + CPLString(osName).toupper())) + { + CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_AppDefined, + "Field '%s' does not exist in layer '%s'%s", + osName.c_str(), poSrcLayer->GetDescription(), + bStrict ? ". Use --lax to ignore it" + : ". It will be ignored"); + if (bStrict) + m_bIsOK = false; + } + } + } + } + + ~GDALVectorFilterAlgorithmLayer() override + { + if (m_poFeatureDefn) + m_poFeatureDefn->Dereference(); + } + + bool IsOK() const + { + return m_bIsOK; + } + + OGRFeatureDefn *GetLayerDefn() override + { + return m_poFeatureDefn; + } + + GIntBig GetFeatureCount(int bForce) override + { + return m_poSrcLayer->GetFeatureCount(bForce); + } + + OGRErr GetExtent(OGREnvelope *psExtent, int bForce) override + { + return m_poSrcLayer->GetExtent(psExtent, bForce); + } + + OGRErr GetExtent(int iGeomField, OGREnvelope *psExtent, int bForce) override + { + return m_poSrcLayer->GetExtent(iGeomField, psExtent, bForce); + } + + void ResetReading() override + { + m_poSrcLayer->ResetReading(); + } + + OGRFeature *GetNextFeature() override + { + auto poSrcFeature = + std::unique_ptr(m_poSrcLayer->GetNextFeature()); + if (!poSrcFeature) + return nullptr; + return TranslateFeature(poSrcFeature.get()).release(); + } + + OGRFeature *GetFeature(GIntBig nFID) override + { + auto poSrcFeature = + std::unique_ptr(m_poSrcLayer->GetFeature(nFID)); + if (!poSrcFeature) + return nullptr; + return TranslateFeature(poSrcFeature.get()).release(); + } + + int TestCapability(const char *pszCap) override + { + if (EQUAL(pszCap, OLCRandomRead) || EQUAL(pszCap, OLCCurveGeometries) || + EQUAL(pszCap, OLCMeasuredGeometries) || + EQUAL(pszCap, OLCZGeometries) || + EQUAL(pszCap, OLCFastFeatureCount) || + EQUAL(pszCap, OLCFastGetExtent) || EQUAL(pszCap, OLCStringsAsUTF8)) + { + return m_poSrcLayer->TestCapability(pszCap); + } + return false; + } +}; + +} // namespace + /************************************************************************/ /* GDALVectorFilterAlgorithm::RunStep() */ /************************************************************************/ @@ -42,6 +276,9 @@ bool GDALVectorFilterAlgorithm::RunStep(GDALProgressFunc, void *) CPLAssert(m_outputDataset.GetName().empty()); CPLAssert(!m_outputDataset.GetDatasetRef()); + auto poSrcDS = m_inputDataset.GetDatasetRef(); + const int nLayerCount = poSrcDS->GetLayerCount(); + bool ret = true; if (m_bbox.size() == 4) { @@ -49,8 +286,6 @@ bool GDALVectorFilterAlgorithm::RunStep(GDALProgressFunc, void *) const double ymin = m_bbox[1]; const double xmax = m_bbox[2]; const double ymax = m_bbox[3]; - auto poSrcDS = m_inputDataset.GetDatasetRef(); - const int nLayerCount = poSrcDS->GetLayerCount(); for (int i = 0; i < nLayerCount; ++i) { auto poSrcLayer = poSrcDS->GetLayer(i); @@ -60,7 +295,42 @@ bool GDALVectorFilterAlgorithm::RunStep(GDALProgressFunc, void *) } } - if (ret) + if (ret && !m_where.empty()) + { + for (int i = 0; i < nLayerCount; ++i) + { + auto poSrcLayer = poSrcDS->GetLayer(i); + ret = ret && (poSrcLayer != nullptr); + if (ret) + ret = poSrcLayer->SetAttributeFilter(m_where.c_str()) == + OGRERR_NONE; + } + } + + if (ret && !m_selectedFields.empty()) + { + auto outDS = std::make_unique(); + outDS->SetDescription(poSrcDS->GetDescription()); + + for (int i = 0; i < nLayerCount; ++i) + { + auto poSrcLayer = poSrcDS->GetLayer(i); + ret = ret && (poSrcLayer != nullptr); + if (ret) + { + auto poLayer = std::make_unique( + poSrcLayer, m_selectedFields, m_strictMode); + ret = poLayer->IsOK(); + if (ret) + { + outDS->AddLayer(std::move(poLayer)); + } + } + } + + m_outputDataset.Set(std::move(outDS)); + } + else if (ret) { m_outputDataset.Set(m_inputDataset.GetDatasetRef()); } diff --git a/apps/gdalalg_vector_filter.h b/apps/gdalalg_vector_filter.h index f2df3ec32b25..e2aac955e692 100644 --- a/apps/gdalalg_vector_filter.h +++ b/apps/gdalalg_vector_filter.h @@ -41,6 +41,9 @@ class GDALVectorFilterAlgorithm /* non final */ bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override; std::vector m_bbox{}; + std::string m_where{}; + std::vector m_selectedFields{}; + bool m_strict = true; }; /************************************************************************/ diff --git a/autotest/utilities/test_gdalalg_vector_filter.py b/autotest/utilities/test_gdalalg_vector_filter.py index f0a62a97c65c..667b0ff2dc75 100755 --- a/autotest/utilities/test_gdalalg_vector_filter.py +++ b/autotest/utilities/test_gdalalg_vector_filter.py @@ -11,7 +11,9 @@ # SPDX-License-Identifier: MIT ############################################################################### -from osgeo import gdal +import pytest + +from osgeo import gdal, ogr def get_filter_alg(): @@ -36,7 +38,7 @@ def test_gdalalg_vector_filter_no_filter(tmp_vsimem): assert ds.GetLayer(0).GetFeatureCount() == 10 -def test_gdalalg_vector_filter_base(tmp_vsimem): +def test_gdalalg_vector_filter_bbox(tmp_vsimem): out_filename = str(tmp_vsimem / "out.shp") @@ -47,3 +49,121 @@ def test_gdalalg_vector_filter_base(tmp_vsimem): with gdal.OpenEx(out_filename) as ds: assert ds.GetLayer(0).GetFeatureCount() == 1 + + +def test_gdalalg_vector_filter_where_discard_all(tmp_vsimem): + + out_filename = str(tmp_vsimem / "out.shp") + + filter_alg = get_filter_alg() + assert filter_alg.ParseRunAndFinalize( + ["--where=0=1", "../ogr/data/poly.shp", out_filename] + ) + + with gdal.OpenEx(out_filename) as ds: + assert ds.GetLayer(0).GetFeatureCount() == 0 + + +def test_gdalalg_vector_filter_where_accept_all(tmp_vsimem): + + out_filename = str(tmp_vsimem / "out.shp") + + filter_alg = get_filter_alg() + assert filter_alg.ParseRunAndFinalize( + ["--where=1=1", "../ogr/data/poly.shp", out_filename] + ) + + with gdal.OpenEx(out_filename) as ds: + assert ds.GetLayer(0).GetFeatureCount() == 10 + + +def test_gdalalg_vector_filter_where_error(tmp_vsimem): + + out_filename = str(tmp_vsimem / "out.shp") + + filter_alg = get_filter_alg() + with pytest.raises( + Exception, match='"invalid" not recognised as an available field.' + ): + filter_alg.ParseRunAndFinalize( + ["--where=invalid", "../ogr/data/poly.shp", out_filename] + ) + + +def test_gdalalg_vector_fields(tmp_vsimem): + + out_filename = str(tmp_vsimem / "out.shp") + + filter_alg = get_filter_alg() + assert filter_alg.ParseRunAndFinalize( + ["--fields=EAS_ID,_ogr_geometry_", "../ogr/data/poly.shp", out_filename] + ) + + with gdal.OpenEx(out_filename) as ds: + lyr = ds.GetLayer(0) + assert lyr.GetLayerDefn().GetFieldCount() == 1 + assert lyr.GetLayerDefn().GetGeomFieldCount() == 1 + assert lyr.GetFeatureCount() == 10 + f = lyr.GetNextFeature() + assert f["EAS_ID"] == 168 + assert f.GetGeometryRef() is not None + lyr.ResetReading() + assert len([f for f in lyr]) == 10 + f = lyr.GetFeature(0) + assert f["EAS_ID"] == 168 + with pytest.raises(Exception): + lyr.GetFeature(10) + assert lyr.TestCapability(ogr.OLCFastFeatureCount) == 1 + assert lyr.TestCapability(ogr.OLCRandomWrite) == 0 + assert lyr.GetExtent() == (478315.53125, 481645.3125, 4762880.5, 4765610.5) + assert lyr.GetExtent(0) == (478315.53125, 481645.3125, 4762880.5, 4765610.5) + + +def test_gdalalg_vector_fields_geom_named(tmp_vsimem): + + src_ds = gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) + src_lyr = src_ds.CreateLayer("test", geom_type=ogr.wkbNone, srs=None) + src_lyr.CreateGeomField(ogr.GeomFieldDefn("geom_field")) + src_lyr.CreateGeomField(ogr.GeomFieldDefn("geom_field2")) + out_filename = str(tmp_vsimem / "out.shp") + + filter_alg = get_filter_alg() + filter_alg.GetArg("input").Get().SetDataset(src_ds) + filter_alg.GetArg("output").Set(out_filename) + assert filter_alg.ParseCommandLineArguments( + ["--of", "Memory", "--fields=geom_field2"] + ) + assert filter_alg.Run() + + ds = filter_alg.GetArg("output").Get().GetDataset() + lyr = ds.GetLayer(0) + assert lyr.GetLayerDefn().GetGeomFieldCount() == 1 + assert lyr.GetLayerDefn().GetGeomFieldDefn(0).GetName() == "geom_field2" + + +def test_gdalalg_vector_fields_non_existing(tmp_vsimem): + + out_filename = str(tmp_vsimem / "out.shp") + + filter_alg = get_filter_alg() + with pytest.raises( + Exception, + match="Field 'i_do_not_exist' does not exist in layer 'poly'. Use --lax to ignore it", + ): + filter_alg.ParseRunAndFinalize( + ["--fields=EAS_ID,i_do_not_exist", "../ogr/data/poly.shp", out_filename] + ) + + filter_alg = get_filter_alg() + assert filter_alg.ParseRunAndFinalize( + [ + "--fields=EAS_ID,_ogr_geometry_", + "../ogr/data/poly.shp", + "--lax", + out_filename, + ] + ) + with gdal.OpenEx(out_filename) as ds: + lyr = ds.GetLayer(0) + assert lyr.GetLayerDefn().GetFieldCount() == 1 + assert lyr.GetLayerDefn().GetGeomFieldCount() == 1 diff --git a/doc/source/conf.py b/doc/source/conf.py index 952b1c723073..7bf69f693e55 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -313,6 +313,13 @@ [author_evenr], 1, ), + ( + "programs/gdal_vector_filter", + "gdal-vector-filter", + "Filter a vector dataset", + [author_evenr], + 1, + ), ( "programs/gdal_vector_pipeline", "gdal-vector-pipeline", diff --git a/doc/source/programs/gdal_vector.rst b/doc/source/programs/gdal_vector.rst index 9b503db16b7a..7e4ac6f0d40b 100644 --- a/doc/source/programs/gdal_vector.rst +++ b/doc/source/programs/gdal_vector.rst @@ -31,6 +31,7 @@ Available sub-commands - :ref:`gdal_vector_clip_subcommand` - :ref:`gdal_vector_convert_subcommand` +- :ref:`gdal_vector_filter_subcommand` - :ref:`gdal_vector_info_subcommand` - :ref:`gdal_vector_pipeline_subcommand` diff --git a/doc/source/programs/gdal_vector_filter.rst b/doc/source/programs/gdal_vector_filter.rst new file mode 100644 index 000000000000..8338e0880afb --- /dev/null +++ b/doc/source/programs/gdal_vector_filter.rst @@ -0,0 +1,150 @@ +.. _gdal_vector_filter_subcommand: + +================================================================================ +"gdal vector filter" sub-command +================================================================================ + +.. versionadded:: 3.11 + +.. only:: html + + Filter a vector dataset. + +.. Index:: gdal vector filter + +Synopsis +-------- + +.. code-block:: + + Usage: gdal vector filter [OPTIONS] + + Clip a vector dataset. + + Positional arguments: + -i, --input Input vector dataset [required] + -o, --output Output vector dataset [required] + + Common Options: + -h, --help Display help message and exit + --version Display GDAL version and exit + --json-usage Display usage as JSON document and exit + --drivers Display driver list as JSON document and exit + --config = Configuration option [may be repeated] + --progress Display progress bar + --strict Strict mode (default: true) + Mutually exclusive with --lax + --lax Relaxed mode + Mutually exclusive with --strict + + Options: + -l, --layer, --input-layer Input layer name(s) [may be repeated] + -f, --of, --format, --output-format Output format + --co, --creation-option = Creation option [may be repeated] + --lco, --layer-creation-option = Layer creation option [may be repeated] + --overwrite Whether overwriting existing output is allowed + --update Whether to open existing dataset in update mode + --overwrite-layer Whether overwriting existing layer is allowed + --append Whether appending to existing layer is allowed + --output-layer Output layer name + --bbox Bounding box as xmin,ymin,xmax,ymax + --where |@ Attribute query in a restricted form of the queries used in the SQL WHERE statement + --fields Selected fields [may be repeated] + + Advanced Options: + --if, --input-format Input formats [may be repeated] + --oo, --open-option Open options [may be repeated] + + +Description +----------- + +:program:`gdal vector filter` can be used to filter a vector dataset from +their spatial extent, a SQL WHERE clause or a subset of fields. + +``filter`` can also be used as a step of :ref:`gdal_vector_pipeline_subcommand`. + +Common options +++++++++++++++ + +.. option:: --strict + + Strict mode (default). Error out when a field specified with :option:`--fields` + does not exist in the source layer(s). + +.. option:: --lax + + Relaxed mode. Only warns when a field specified with :option:`--fields` + does not exist in the source layer(s). + + +Standard options +++++++++++++++++ + +.. include:: gdal_options/of_vector.rst + +.. include:: gdal_options/co_vector.rst + +.. include:: gdal_options/overwrite.rst + +.. option:: --bbox ,,, + + Bounds to which to filter the dataset. They are assumed to be in the CRS of + the input dataset. + The X and Y axis are the "GIS friendly ones", that is X is longitude or easting, + and Y is latitude or northing. + Note that filtering does not clip geometries to the bounding box. + +.. option:: --where |@ + + Attribute query (like SQL WHERE) + +.. option:: --fields + + Comma-separated list of fields from input layer to copy to the new layer. + + Field names with spaces, commas or double-quote + should be surrounded with a starting and ending double-quote character, and + double-quote characters in a field name should be escaped with backslash. + + Depending on the shell used, this might require further quoting. For example, + to select ``regular_field``, ``a_field_with space, and comma`` and + ``a field with " double quote`` with a Unix shell: + + .. code-block:: bash + + --fields "regular_field,\"a_field_with space, and comma\",\"a field with \\\" double quote\"" + + A field is only selected once, even if mentioned several times in the list. + + Geometry fields can also be specified in the list. If the source layer has + no explicit name for the geometry field, ``_ogr_geometry_`` must be used to + select the unique geometry field. + + By default, specifying a non-existing source field name results in an error, + unless :option:`--lax` is specified. + + +Advanced options +++++++++++++++++ + +.. include:: gdal_options/oo.rst + +.. include:: gdal_options/if.rst + +Examples +-------- + +.. example:: + :title: Select features from a GeoPackage file that intersect the bounding box from longitude 2, latitude 49, to longitude 3, latitude 50 in WGS 84 + + .. code-block:: bash + + $ gdal vector filter --bbox=2,49,3,50 in.gpkg out.gpkg --overwrite + +.. example:: + :title: Select the EAS_ID field and the geometry field from a Shapefile + + .. code-block:: bash + + $ gdal vector filter --fields=EAS_ID,_ogr_geometry_ in.shp out.gpkg --overwrite diff --git a/doc/source/programs/gdal_vector_pipeline.rst b/doc/source/programs/gdal_vector_pipeline.rst index 20d6b756fccd..6a010ca031a8 100644 --- a/doc/source/programs/gdal_vector_pipeline.rst +++ b/doc/source/programs/gdal_vector_pipeline.rst @@ -82,6 +82,12 @@ Details for options can be found in :ref:`gdal_vector_clip_subcommand`. Options: --bbox Bounding box as xmin,ymin,xmax,ymax + --where |@ Attribute query in a restricted form of the queries used in the SQL WHERE statement + --fields Selected fields [may be repeated] + + +Details for options can be found in :ref:`gdal_vector_filter_subcommand`. + * reproject [OPTIONS] diff --git a/doc/source/programs/index.rst b/doc/source/programs/index.rst index 95477fda0777..0a0d8454825a 100644 --- a/doc/source/programs/index.rst +++ b/doc/source/programs/index.rst @@ -45,6 +45,7 @@ single :program:`gdal` program that accepts commands and subcommands. gdal_vector_info gdal_vector_clip gdal_vector_convert + gdal_vector_filter gdal_vector_pipeline .. only:: html @@ -68,6 +69,7 @@ single :program:`gdal` program that accepts commands and subcommands. - :ref:`gdal_vector_command`: Entry point for vector commands - :ref:`gdal_vector_info_subcommand`: Get information on a vector dataset - :ref:`gdal_vector_clip_subcommand`: Clip a vector dataset + - :ref:`gdal_vector_filter_subcommand`: Filter a vector dataset - :ref:`gdal_vector_convert_subcommand`: Convert a vector dataset - :ref:`gdal_vector_pipeline_subcommand`: Process a vector dataset diff --git a/doc/source/programs/migration_guide_to_gdal_cli.rst b/doc/source/programs/migration_guide_to_gdal_cli.rst index 31952e6065c0..4f47077db1a2 100644 --- a/doc/source/programs/migration_guide_to_gdal_cli.rst +++ b/doc/source/programs/migration_guide_to_gdal_cli.rst @@ -173,7 +173,7 @@ Vector commands gdal vector filter --bbox=2,49,3,50 in.gpkg out.gpkg -* Selecting features from a GeoPackage file intersecting a bounding box, but not clipping them to it and reprojecting +* Selecting features from a shapefile intersecting a bounding box, but not clipping them to it and reprojecting .. code-block:: @@ -182,3 +182,14 @@ Vector commands ==> gdal vector pipeline read in.gpkg ! filter --bbox=2,49,3,50 ! reproject --dst-crs=EPSG:32631 ! write out.gpkg + + +* Selecting features from a shapefile based on an attribute query, and restricting to a few fields + +.. code-block:: + + ogr2ogr -where "country='Greenland'" -select population,_ogr_geometry_ out.gpkg in.shp + + ==> + + gdal vector filter --where "country='Greenland'" --fields population,_ogr_geometry_ in.shp out.gpkg