LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_sort.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 109 114 95.6 %
Date: 2025-12-13 23:48:27 Functions: 11 11 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             : *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "gdal vector sort" subcommand
       5             :  * Author:   Daniel Baston
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, ISciences LLC
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vector_sort.h"
      14             : 
      15             : #include "cpl_error.h"
      16             : #include "gdal_alg.h"
      17             : #include "gdal_priv.h"
      18             : #include "gdalalg_vector_geom.h"
      19             : #include "ogr_geometry.h"
      20             : 
      21             : #include "ogr_geos.h"
      22             : 
      23             : #include <cinttypes>
      24             : #include <limits>
      25             : 
      26             : #ifndef _
      27             : #define _(x) (x)
      28             : #endif
      29             : 
      30             : //! @cond Doxygen_Suppress
      31             : 
      32          49 : GDALVectorSortAlgorithm::GDALVectorSortAlgorithm(bool standaloneStep)
      33             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      34          49 :                                       standaloneStep)
      35             : {
      36             :     AddArg("geometry-field", 0, _("Name of geometry field to use in sort"),
      37          49 :            &m_geomField);
      38          98 :     AddArg("method", 0, _("Geometry sorting algorithm"), &m_sortMethod)
      39          49 :         .SetChoices("hilbert", "strtree")
      40          49 :         .SetDefault(m_sortMethod);
      41          49 : }
      42             : 
      43             : namespace
      44             : {
      45             : 
      46           8 : bool CreateDstFeatures(
      47             :     const std::vector<std::unique_ptr<OGRFeature>> &srcFeatures,
      48             :     const std::vector<size_t> &sortedIndices, OGRLayer &dstLayer)
      49             : {
      50         428 :     for (size_t iSrcFeature : sortedIndices)
      51             :     {
      52         420 :         OGRFeature *poSrcFeature = srcFeatures[iSrcFeature].get();
      53         420 :         poSrcFeature->SetFDefnUnsafe(dstLayer.GetLayerDefn());
      54         420 :         poSrcFeature->SetFID(OGRNullFID);
      55             : 
      56         420 :         if (dstLayer.CreateFeature(poSrcFeature) != OGRERR_NONE)
      57             :         {
      58           0 :             return false;
      59             :         }
      60             :     }
      61             : 
      62           8 :     return true;
      63             : }
      64             : 
      65             : class GDALVectorHilbertSortDataset
      66             :     : public GDALVectorNonStreamingAlgorithmDataset
      67             : {
      68             :   public:
      69             :     using GDALVectorNonStreamingAlgorithmDataset::
      70             :         GDALVectorNonStreamingAlgorithmDataset;
      71             : 
      72           5 :     bool Process(OGRLayer &srcLayer, OGRLayer &dstLayer,
      73             :                  int geomFieldIndex) override
      74             :     {
      75          10 :         std::vector<std::unique_ptr<OGRFeature>> features;
      76             : 
      77         225 :         for (auto &feature : srcLayer)
      78             :         {
      79         220 :             features.emplace_back(feature.release());
      80             :         }
      81           5 :         OGREnvelope oLayerExtent;
      82             : 
      83          10 :         std::vector<OGREnvelope> envelopes(features.size());
      84         225 :         for (size_t i = 0; i < features.size(); i++)
      85             :         {
      86         220 :             const OGRFeature *poFeature = features[i].get();
      87             :             const OGRGeometry *poGeom =
      88         220 :                 poFeature->GetGeomFieldRef(geomFieldIndex);
      89             : 
      90         220 :             if (poGeom != nullptr && !poGeom->IsEmpty())
      91             :             {
      92         212 :                 poGeom->getEnvelope(&envelopes[i]);
      93         212 :                 oLayerExtent.Merge(envelopes[i]);
      94             :             }
      95             :         }
      96             : 
      97             :         std::vector<std::pair<std::size_t, std::uint32_t>> hilbertCodes(
      98          10 :             features.size());
      99         225 :         for (std::size_t i = 0; i < features.size(); i++)
     100             :         {
     101         220 :             hilbertCodes[i].first = i;
     102             : 
     103         220 :             if (envelopes[i].IsInit())
     104             :             {
     105             :                 double dfX, dfY;
     106         212 :                 envelopes[i].Center(dfX, dfY);
     107         212 :                 hilbertCodes[i].second =
     108         212 :                     GDALHilbertCode(&oLayerExtent, dfX, dfY);
     109             :             }
     110             :             else
     111             :             {
     112           8 :                 hilbertCodes[i].second =
     113           8 :                     std::numeric_limits<std::uint32_t>::max();
     114             :             }
     115             :         }
     116             : 
     117           5 :         std::sort(hilbertCodes.begin(), hilbertCodes.end(),
     118        1455 :                   [](const auto &a, const auto &b)
     119        1455 :                   { return a.second < b.second; });
     120             : 
     121          10 :         std::vector<size_t> sortedIndices;
     122         225 :         for (const auto &sItem : hilbertCodes)
     123             :         {
     124         220 :             sortedIndices.push_back(sItem.first);
     125             :         }
     126             : 
     127          10 :         return CreateDstFeatures(features, sortedIndices, dstLayer);
     128             :     }
     129             : 
     130             :   private:
     131             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorHilbertSortDataset)
     132             : };
     133             : 
     134             : #ifdef HAVE_GEOS
     135             : class GDALVectorSTRTreeSortDataset
     136             :     : public GDALVectorNonStreamingAlgorithmDataset
     137             : {
     138             :   public:
     139             :     using GDALVectorNonStreamingAlgorithmDataset::
     140             :         GDALVectorNonStreamingAlgorithmDataset;
     141             : 
     142           8 :     ~GDALVectorSTRTreeSortDataset() override
     143           4 :     {
     144           4 :         if (m_geosContext)
     145             :         {
     146           3 :             finishGEOS_r(m_geosContext);
     147             :         }
     148           8 :     }
     149             : 
     150           3 :     bool Process(OGRLayer &srcLayer, OGRLayer &dstLayer,
     151             :                  int geomFieldIndex) override
     152             :     {
     153           6 :         std::vector<std::unique_ptr<OGRFeature>> features;
     154           6 :         std::vector<size_t> sortedIndices;
     155             : 
     156         203 :         for (auto &feature : srcLayer)
     157             :         {
     158         200 :             features.emplace_back(feature.release());
     159             :         }
     160             :         // TODO: variant of this fn returning unique_ptr
     161           3 :         m_geosContext = OGRGeometry::createGEOSContext();
     162             : 
     163           3 :         auto TreeDeleter = [this](GEOSSTRtree *tree)
     164           3 :         { GEOSSTRtree_destroy_r(m_geosContext, tree); };
     165             : 
     166             :         std::unique_ptr<GEOSSTRtree, decltype(TreeDeleter)> poTree(
     167           6 :             GEOSSTRtree_create_r(m_geosContext, 10), TreeDeleter);
     168             : 
     169           3 :         OGREnvelope oGeomExtent;
     170           6 :         std::vector<size_t> nullIndices;
     171         203 :         for (size_t i = 0; i < features.size(); i++)
     172             :         {
     173         200 :             const OGRFeature *poFeature = features[i].get();
     174             :             const OGRGeometry *poGeom =
     175         200 :                 poFeature->GetGeomFieldRef(geomFieldIndex);
     176             : 
     177         200 :             if (poGeom == nullptr || poGeom->IsEmpty())
     178             :             {
     179           8 :                 nullIndices.push_back(i);
     180           8 :                 continue;
     181             :             }
     182             : 
     183         192 :             poGeom->getEnvelope(&oGeomExtent);
     184         192 :             GEOSGeometry *poEnv = CreateGEOSEnvelope(oGeomExtent);
     185         192 :             if (poEnv == nullptr)
     186             :             {
     187           0 :                 return false;
     188             :             }
     189             : 
     190         192 :             GEOSSTRtree_insert_r(m_geosContext, poTree.get(), poEnv,
     191             :                                  reinterpret_cast<void *>(i));
     192         192 :             GEOSGeom_destroy_r(m_geosContext, poEnv);
     193             :         }
     194             : 
     195             : #if GEOS_VERSION_MAJOR > 3 ||                                                  \
     196             :     (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12)
     197           3 :         GEOSSTRtree_build_r(m_geosContext, poTree.get());
     198             : #else
     199             :         if (!features.empty())
     200             :         {
     201             :             // Perform a dummy query to force tree construction.
     202             :             GEOSGeometry *poEnv = CreateGEOSEnvelope(oGeomExtent);
     203             :             GEOSSTRtree_query_r(
     204             :                 m_geosContext, poTree.get(), poEnv, [](void *, void *) {},
     205             :                 nullptr);
     206             :             GEOSGeom_destroy_r(m_geosContext, poEnv);
     207             :         }
     208             : #endif
     209             : 
     210           3 :         GEOSSTRtree_iterate_r(
     211             :             m_geosContext, poTree.get(),
     212         192 :             [](void *item, void *userData)
     213             :             {
     214         192 :                 static_cast<std::vector<size_t> *>(userData)->push_back(
     215         192 :                     reinterpret_cast<size_t>(item));
     216         192 :             },
     217             :             &sortedIndices);
     218             : 
     219          11 :         for (size_t nullInd : nullIndices)
     220             :         {
     221           8 :             sortedIndices.push_back(nullInd);
     222             :         }
     223             : 
     224           3 :         return CreateDstFeatures(features, sortedIndices, dstLayer);
     225             :     }
     226             : 
     227             :   private:
     228             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorSTRTreeSortDataset)
     229             : 
     230             :     // FIXME: Duplicated from alg/zonal.cpp.
     231             :     // Put into OGRGeometryFactory?
     232         192 :     GEOSGeometry *CreateGEOSEnvelope(const OGREnvelope &oEnv) const
     233             :     {
     234         192 :         GEOSCoordSequence *seq = GEOSCoordSeq_create_r(m_geosContext, 2, 2);
     235         192 :         if (seq == nullptr)
     236             :         {
     237           0 :             return nullptr;
     238             :         }
     239         192 :         GEOSCoordSeq_setXY_r(m_geosContext, seq, 0, oEnv.MinX, oEnv.MinY);
     240         192 :         GEOSCoordSeq_setXY_r(m_geosContext, seq, 1, oEnv.MaxX, oEnv.MaxY);
     241         192 :         return GEOSGeom_createLineString_r(m_geosContext, seq);
     242             :     }
     243             : 
     244             :     GEOSContextHandle_t m_geosContext{nullptr};
     245             : };
     246             : #endif
     247             : 
     248             : }  // namespace
     249             : 
     250          12 : bool GDALVectorSortAlgorithm::RunStep(GDALPipelineStepRunContext &)
     251             : {
     252          12 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     253          12 :     std::unique_ptr<GDALVectorNonStreamingAlgorithmDataset> poDstDS;
     254             : 
     255          12 :     if (m_sortMethod == "hilbert")
     256             :     {
     257           8 :         poDstDS = std::make_unique<GDALVectorHilbertSortDataset>();
     258             :     }
     259             :     else
     260             :     {
     261             :         // Already checked for invalid method at arg parsing stage.
     262           4 :         CPLAssert(m_sortMethod == "strtree");
     263             : #ifdef HAVE_GEOS
     264           4 :         poDstDS = std::make_unique<GDALVectorSTRTreeSortDataset>();
     265             : #else
     266             :         CPLError(
     267             :             CE_Failure, CPLE_AppDefined,
     268             :             "--method strtree requires a GDAL build against the GEOS library.");
     269             :         return false;
     270             : #endif
     271             :     }
     272             : 
     273          22 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
     274             :     {
     275          12 :         if (m_inputLayerNames.empty() ||
     276           0 :             std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
     277          12 :                       poSrcLayer->GetDescription()) != m_inputLayerNames.end())
     278             :         {
     279          12 :             const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
     280          12 :             if (poSrcLayerDefn->GetGeomFieldCount() > 0)
     281             :             {
     282             :                 const int geomFieldIndex =
     283          10 :                     m_geomField.empty() ? 0
     284           3 :                                         : poSrcLayerDefn->GetGeomFieldIndex(
     285           3 :                                               m_geomField.c_str());
     286             : 
     287          10 :                 if (geomFieldIndex == -1)
     288             :                 {
     289           2 :                     ReportError(
     290             :                         CE_Failure, CPLE_AppDefined,
     291             :                         "Specified geometry field '%s' does not exist in "
     292             :                         "layer '%s'",
     293           2 :                         m_geomField.c_str(), poSrcLayer->GetDescription());
     294           2 :                     return false;
     295             :                 }
     296             : 
     297          16 :                 if (!poDstDS->AddProcessedLayer(*poSrcLayer,
     298           8 :                                                 *poSrcLayer->GetLayerDefn(),
     299             :                                                 geomFieldIndex))
     300             :                 {
     301           0 :                     return false;
     302             :                 }
     303             :             }
     304           2 :             else if (m_inputLayerNames.empty())
     305             :             {
     306           2 :                 poDstDS->AddPassThroughLayer(*poSrcLayer);
     307             :             }
     308             :         }
     309             :     }
     310             : 
     311          10 :     m_outputDataset.Set(std::move(poDstDS));
     312             : 
     313          10 :     return true;
     314             : }
     315             : 
     316             : GDALVectorSortAlgorithmStandalone::~GDALVectorSortAlgorithmStandalone() =
     317             :     default;
     318             : 
     319             : //! @endcond

Generated by: LCOV version 1.14