LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_sort.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 141 152 92.8 %
Date: 2026-01-11 15:50:51 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_enumerate.h"
      16             : #include "cpl_error.h"
      17             : #include "gdal_alg.h"
      18             : #include "gdal_priv.h"
      19             : #include "gdalalg_vector_geom.h"
      20             : #include "ogr_geometry.h"
      21             : 
      22             : #include "ogr_geos.h"
      23             : 
      24             : #include <algorithm>
      25             : #include <cinttypes>
      26             : #include <limits>
      27             : 
      28             : #ifndef _
      29             : #define _(x) (x)
      30             : #endif
      31             : 
      32             : //! @cond Doxygen_Suppress
      33             : 
      34          50 : GDALVectorSortAlgorithm::GDALVectorSortAlgorithm(bool standaloneStep)
      35             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      36          50 :                                       standaloneStep)
      37             : {
      38             :     AddArg("geometry-field", 0, _("Name of geometry field to use in sort"),
      39          50 :            &m_geomField);
      40         100 :     AddArg("method", 0, _("Geometry sorting algorithm"), &m_sortMethod)
      41          50 :         .SetChoices("hilbert", "strtree")
      42          50 :         .SetDefault(m_sortMethod);
      43          50 : }
      44             : 
      45             : namespace
      46             : {
      47             : 
      48           8 : bool CreateDstFeatures(std::vector<std::unique_ptr<OGRFeature>> &srcFeatures,
      49             :                        const std::vector<size_t> &sortedIndices,
      50             :                        OGRLayer &dstLayer, GDALProgressFunc pfnProgress,
      51             :                        void *pProgressData, double dfProgressStart,
      52             :                        double dfProgressRatio)
      53             : {
      54           8 :     uint64_t nCounter = 0;
      55         428 :     for (size_t iSrcFeature : sortedIndices)
      56             :     {
      57         420 :         auto &poSrcFeature = srcFeatures[iSrcFeature];
      58         420 :         poSrcFeature->SetFDefnUnsafe(dstLayer.GetLayerDefn());
      59         420 :         poSrcFeature->SetFID(OGRNullFID);
      60             : 
      61         420 :         if (dstLayer.CreateFeature(std::move(poSrcFeature)) != OGRERR_NONE)
      62             :         {
      63           0 :             return false;
      64             :         }
      65         692 :         if (pfnProgress &&
      66         272 :             !pfnProgress(dfProgressStart +
      67         272 :                              static_cast<double>(++nCounter) * dfProgressRatio,
      68             :                          "", pProgressData))
      69             :         {
      70           0 :             CPLError(CE_Failure, CPLE_UserInterrupt, "Interrupted by user");
      71           0 :             return false;
      72             :         }
      73             :     }
      74             : 
      75           8 :     return true;
      76             : }
      77             : 
      78             : class GDALVectorHilbertSortDataset
      79             :     : public GDALVectorNonStreamingAlgorithmDataset
      80             : {
      81             :   public:
      82             :     using GDALVectorNonStreamingAlgorithmDataset::
      83             :         GDALVectorNonStreamingAlgorithmDataset;
      84             : 
      85           5 :     bool Process(OGRLayer &srcLayer, OGRLayer &dstLayer, int geomFieldIndex,
      86             :                  GDALProgressFunc pfnProgress, void *pProgressData) override
      87             :     {
      88          10 :         std::vector<std::unique_ptr<OGRFeature>> features;
      89             : 
      90             :         const GIntBig nLayerFeatures =
      91           5 :             srcLayer.TestCapability(OLCFastFeatureCount)
      92           5 :                 ? srcLayer.GetFeatureCount(false)
      93           5 :                 : -1;
      94             :         const double dfInvLayerFeatures =
      95           5 :             1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
      96           5 :         const double dfFirstPhaseProgressRatio =
      97             :             dfInvLayerFeatures * (2.0 / 3.0);
      98         225 :         for (auto &feature : srcLayer)
      99             :         {
     100         220 :             features.emplace_back(feature.release());
     101         356 :             if (pfnProgress && nLayerFeatures > 0 &&
     102         136 :                 !pfnProgress(static_cast<double>(features.size()) *
     103             :                                  dfFirstPhaseProgressRatio,
     104             :                              "", pProgressData))
     105             :             {
     106           0 :                 ReportError(CE_Failure, CPLE_UserInterrupt,
     107             :                             "Interrupted by user");
     108           0 :                 return false;
     109             :             }
     110             :         }
     111           5 :         OGREnvelope oLayerExtent;
     112             : 
     113          10 :         std::vector<OGREnvelope> envelopes(features.size());
     114         225 :         for (const auto &[i, poFeature] : cpl::enumerate(features))
     115             :         {
     116             :             const OGRGeometry *poGeom =
     117         220 :                 poFeature->GetGeomFieldRef(geomFieldIndex);
     118             : 
     119         220 :             if (poGeom != nullptr && !poGeom->IsEmpty())
     120             :             {
     121         212 :                 poGeom->getEnvelope(&envelopes[i]);
     122         212 :                 oLayerExtent.Merge(envelopes[i]);
     123             :             }
     124             :         }
     125             : 
     126             :         std::vector<std::pair<std::size_t, std::uint32_t>> hilbertCodes(
     127          10 :             features.size());
     128         225 :         for (std::size_t i = 0; i < features.size(); i++)
     129             :         {
     130         220 :             hilbertCodes[i].first = i;
     131             : 
     132         220 :             if (envelopes[i].IsInit())
     133             :             {
     134             :                 double dfX, dfY;
     135         212 :                 envelopes[i].Center(dfX, dfY);
     136         212 :                 hilbertCodes[i].second =
     137         212 :                     GDALHilbertCode(&oLayerExtent, dfX, dfY);
     138             :             }
     139             :             else
     140             :             {
     141           8 :                 hilbertCodes[i].second =
     142           8 :                     std::numeric_limits<std::uint32_t>::max();
     143             :             }
     144             :         }
     145             : 
     146           5 :         std::sort(hilbertCodes.begin(), hilbertCodes.end(),
     147        1455 :                   [](const auto &a, const auto &b)
     148        1455 :                   { return a.second < b.second; });
     149             : 
     150          10 :         std::vector<size_t> sortedIndices;
     151         225 :         for (const auto &sItem : hilbertCodes)
     152             :         {
     153         220 :             sortedIndices.push_back(sItem.first);
     154             :         }
     155             : 
     156           5 :         const double dfProgressStart = nLayerFeatures > 0 ? 2.0 / 3.0 : 0.0;
     157             :         const double dfProgressRatio =
     158           5 :             (nLayerFeatures > 0 ? 1.0 / 3.0 : 1.0) /
     159           5 :             std::max(1.0, static_cast<double>(features.size()));
     160           5 :         return CreateDstFeatures(features, sortedIndices, dstLayer, pfnProgress,
     161             :                                  pProgressData, dfProgressStart,
     162           5 :                                  dfProgressRatio);
     163             :     }
     164             : 
     165             :   private:
     166             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorHilbertSortDataset)
     167             : };
     168             : 
     169             : #ifdef HAVE_GEOS
     170             : class GDALVectorSTRTreeSortDataset
     171             :     : public GDALVectorNonStreamingAlgorithmDataset
     172             : {
     173             :   public:
     174             :     using GDALVectorNonStreamingAlgorithmDataset::
     175             :         GDALVectorNonStreamingAlgorithmDataset;
     176             : 
     177           8 :     ~GDALVectorSTRTreeSortDataset() override
     178           4 :     {
     179           4 :         if (m_geosContext)
     180             :         {
     181           3 :             finishGEOS_r(m_geosContext);
     182             :         }
     183           8 :     }
     184             : 
     185           3 :     bool Process(OGRLayer &srcLayer, OGRLayer &dstLayer, int geomFieldIndex,
     186             :                  GDALProgressFunc pfnProgress, void *pProgressData) override
     187             :     {
     188           6 :         std::vector<std::unique_ptr<OGRFeature>> features;
     189           6 :         std::vector<size_t> sortedIndices;
     190             : 
     191             :         const GIntBig nLayerFeatures =
     192           3 :             srcLayer.TestCapability(OLCFastFeatureCount)
     193           3 :                 ? srcLayer.GetFeatureCount(false)
     194           3 :                 : -1;
     195             :         const double dfInvLayerFeatures =
     196           3 :             1.0 / std::max(1.0, static_cast<double>(nLayerFeatures));
     197           3 :         const double dfFirstPhaseProgressRatio =
     198             :             dfInvLayerFeatures * (2.0 / 3.0);
     199         203 :         for (auto &feature : srcLayer)
     200             :         {
     201         200 :             features.emplace_back(feature.release());
     202         336 :             if (pfnProgress && nLayerFeatures > 0 &&
     203         136 :                 !pfnProgress(static_cast<double>(features.size()) *
     204             :                                  dfFirstPhaseProgressRatio,
     205             :                              "", pProgressData))
     206             :             {
     207           0 :                 ReportError(CE_Failure, CPLE_UserInterrupt,
     208             :                             "Interrupted by user");
     209           0 :                 return false;
     210             :             }
     211             :         }
     212             :         // TODO: variant of this fn returning unique_ptr
     213           3 :         m_geosContext = OGRGeometry::createGEOSContext();
     214             : 
     215           3 :         auto TreeDeleter = [this](GEOSSTRtree *tree)
     216           3 :         { GEOSSTRtree_destroy_r(m_geosContext, tree); };
     217             : 
     218             :         std::unique_ptr<GEOSSTRtree, decltype(TreeDeleter)> poTree(
     219           6 :             GEOSSTRtree_create_r(m_geosContext, 10), TreeDeleter);
     220             : 
     221           3 :         OGREnvelope oGeomExtent;
     222           6 :         std::vector<size_t> nullIndices;
     223         203 :         for (const auto &[i, poFeature] : cpl::enumerate(features))
     224             :         {
     225             :             const OGRGeometry *poGeom =
     226         200 :                 poFeature->GetGeomFieldRef(geomFieldIndex);
     227             : 
     228         200 :             if (poGeom == nullptr || poGeom->IsEmpty())
     229             :             {
     230           8 :                 nullIndices.push_back(i);
     231           8 :                 continue;
     232             :             }
     233             : 
     234         192 :             poGeom->getEnvelope(&oGeomExtent);
     235         192 :             GEOSGeometry *poEnv = CreateGEOSEnvelope(oGeomExtent);
     236         192 :             if (poEnv == nullptr)
     237             :             {
     238           0 :                 return false;
     239             :             }
     240             : 
     241         192 :             GEOSSTRtree_insert_r(m_geosContext, poTree.get(), poEnv,
     242         192 :                                  reinterpret_cast<void *>(i));
     243         192 :             GEOSGeom_destroy_r(m_geosContext, poEnv);
     244             :         }
     245             : 
     246             : #if GEOS_VERSION_MAJOR > 3 ||                                                  \
     247             :     (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 12)
     248           3 :         GEOSSTRtree_build_r(m_geosContext, poTree.get());
     249             : #else
     250             :         if (!features.empty())
     251             :         {
     252             :             // Perform a dummy query to force tree construction.
     253             :             GEOSGeometry *poEnv = CreateGEOSEnvelope(oGeomExtent);
     254             :             GEOSSTRtree_query_r(
     255             :                 m_geosContext, poTree.get(), poEnv, [](void *, void *) {},
     256             :                 nullptr);
     257             :             GEOSGeom_destroy_r(m_geosContext, poEnv);
     258             :         }
     259             : #endif
     260             : 
     261           3 :         GEOSSTRtree_iterate_r(
     262             :             m_geosContext, poTree.get(),
     263         192 :             [](void *item, void *userData)
     264             :             {
     265         192 :                 static_cast<std::vector<size_t> *>(userData)->push_back(
     266         192 :                     reinterpret_cast<size_t>(item));
     267         192 :             },
     268             :             &sortedIndices);
     269             : 
     270           3 :         sortedIndices.insert(sortedIndices.end(), nullIndices.begin(),
     271           6 :                              nullIndices.end());
     272           3 :         nullIndices.clear();
     273             : 
     274           3 :         const double dfProgressStart = nLayerFeatures > 0 ? 2.0 / 3.0 : 0.0;
     275             :         const double dfProgressRatio =
     276           3 :             (nLayerFeatures > 0 ? 1.0 / 3.0 : 1.0) /
     277           3 :             std::max(1.0, static_cast<double>(features.size()));
     278           3 :         return CreateDstFeatures(features, sortedIndices, dstLayer, pfnProgress,
     279             :                                  pProgressData, dfProgressStart,
     280           3 :                                  dfProgressRatio);
     281             :     }
     282             : 
     283             :   private:
     284             :     CPL_DISALLOW_COPY_ASSIGN(GDALVectorSTRTreeSortDataset)
     285             : 
     286             :     // FIXME: Duplicated from alg/zonal.cpp.
     287             :     // Put into OGRGeometryFactory?
     288         192 :     GEOSGeometry *CreateGEOSEnvelope(const OGREnvelope &oEnv) const
     289             :     {
     290         192 :         GEOSCoordSequence *seq = GEOSCoordSeq_create_r(m_geosContext, 2, 2);
     291         192 :         if (seq == nullptr)
     292             :         {
     293           0 :             return nullptr;
     294             :         }
     295         192 :         GEOSCoordSeq_setXY_r(m_geosContext, seq, 0, oEnv.MinX, oEnv.MinY);
     296         192 :         GEOSCoordSeq_setXY_r(m_geosContext, seq, 1, oEnv.MaxX, oEnv.MaxY);
     297         192 :         return GEOSGeom_createLineString_r(m_geosContext, seq);
     298             :     }
     299             : 
     300             :     GEOSContextHandle_t m_geosContext{nullptr};
     301             : };
     302             : #endif
     303             : 
     304             : }  // namespace
     305             : 
     306          12 : bool GDALVectorSortAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     307             : {
     308          12 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     309          12 :     std::unique_ptr<GDALVectorNonStreamingAlgorithmDataset> poDstDS;
     310             : 
     311          12 :     if (m_sortMethod == "hilbert")
     312             :     {
     313           8 :         poDstDS = std::make_unique<GDALVectorHilbertSortDataset>();
     314             :     }
     315             :     else
     316             :     {
     317             :         // Already checked for invalid method at arg parsing stage.
     318           4 :         CPLAssert(m_sortMethod == "strtree");
     319             : #ifdef HAVE_GEOS
     320           4 :         poDstDS = std::make_unique<GDALVectorSTRTreeSortDataset>();
     321             : #else
     322             :         CPLError(
     323             :             CE_Failure, CPLE_AppDefined,
     324             :             "--method strtree requires a GDAL build against the GEOS library.");
     325             :         return false;
     326             : #endif
     327             :     }
     328             : 
     329          24 :     GDALVectorAlgorithmLayerProgressHelper progressHelper(ctxt);
     330             : 
     331          24 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
     332             :     {
     333          12 :         if (m_inputLayerNames.empty() ||
     334           0 :             std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
     335          12 :                       poSrcLayer->GetDescription()) != m_inputLayerNames.end())
     336             :         {
     337          12 :             const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
     338          12 :             if (poSrcLayerDefn->GetGeomFieldCount() > 0)
     339             :             {
     340          10 :                 progressHelper.AddProcessedLayer(*poSrcLayer);
     341             :             }
     342             :             else
     343             :             {
     344           2 :                 progressHelper.AddPassThroughLayer(*poSrcLayer);
     345             :             }
     346             :         }
     347             :     }
     348             : 
     349          22 :     for (auto [poSrcLayer, bProcessed, layerProgressFunc, layerProgressData] :
     350          32 :          progressHelper)
     351             :     {
     352          12 :         if (bProcessed)
     353             :         {
     354          10 :             const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
     355             :             const int geomFieldIndex =
     356          10 :                 m_geomField.empty()
     357          10 :                     ? 0
     358           3 :                     : poSrcLayerDefn->GetGeomFieldIndex(m_geomField.c_str());
     359             : 
     360          10 :             if (geomFieldIndex == -1)
     361             :             {
     362           2 :                 ReportError(CE_Failure, CPLE_AppDefined,
     363             :                             "Specified geometry field '%s' does not exist in "
     364             :                             "layer '%s'",
     365           2 :                             m_geomField.c_str(), poSrcLayer->GetDescription());
     366           2 :                 return false;
     367             :             }
     368             : 
     369          16 :             if (!poDstDS->AddProcessedLayer(
     370           8 :                     *poSrcLayer, *poSrcLayer->GetLayerDefn(), geomFieldIndex,
     371             :                     layerProgressFunc, layerProgressData.get()))
     372             :             {
     373           0 :                 return false;
     374             :             }
     375             :         }
     376             :         else
     377             :         {
     378           2 :             poDstDS->AddPassThroughLayer(*poSrcLayer);
     379             :         }
     380             :     }
     381             : 
     382          10 :     m_outputDataset.Set(std::move(poDstDS));
     383             : 
     384          10 :     return true;
     385             : }
     386             : 
     387             : GDALVectorSortAlgorithmStandalone::~GDALVectorSortAlgorithmStandalone() =
     388             :     default;
     389             : 
     390             : //! @endcond

Generated by: LCOV version 1.14