LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_check_geometry.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 124 129 96.1 %
Date: 2025-08-19 18:03:11 Functions: 8 10 80.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             : *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "gdal vector check-geometry" 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_check_geometry.h"
      14             : 
      15             : #include "cpl_error.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdalalg_vector_geom.h"
      18             : #include "ogr_geometry.h"
      19             : #include "ogr_geos.h"
      20             : 
      21             : #include <cinttypes>
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : //! @cond Doxygen_Suppress
      28             : 
      29          38 : GDALVectorCheckGeometryAlgorithm::GDALVectorCheckGeometryAlgorithm(
      30          38 :     bool standaloneStep)
      31             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      32          38 :                                       standaloneStep)
      33             : {
      34             :     AddArg("include-valid", 0,
      35             :            _("Include valid inputs in output, with empty geometry"),
      36          38 :            &m_includeValid);
      37             : 
      38             :     AddArg("geometry-field", 0, _("Name of geometry field to check"),
      39          38 :            &m_geomField);
      40          38 : }
      41             : 
      42             : #ifdef HAVE_GEOS
      43             : 
      44             : class GDALInvalidLocationLayer final : public GDALVectorPipelineOutputLayer
      45             : {
      46             :   private:
      47             :     static constexpr const char *ERROR_DESCRIPTION_FIELD = "error";
      48             : 
      49             :   public:
      50          17 :     GDALInvalidLocationLayer(OGRLayer &layer, bool bSingleLayerOutput,
      51             :                              int srcGeomField, bool skipValid)
      52          17 :         : GDALVectorPipelineOutputLayer(layer),
      53          17 :           m_defn(OGRFeatureDefn::CreateFeatureDefn(
      54             :               bSingleLayerOutput ? "error_location"
      55          19 :                                  : std::string("error_location_")
      56           2 :                                        .append(layer.GetDescription())
      57           2 :                                        .c_str())),
      58          17 :           m_geosContext(OGRGeometry::createGEOSContext()),
      59          34 :           m_srcGeomField(srcGeomField), m_skipValid(skipValid)
      60             :     {
      61          17 :         m_defn->Reference();
      62             : 
      63             :         auto poDescriptionFieldDefn =
      64          34 :             std::make_unique<OGRFieldDefn>(ERROR_DESCRIPTION_FIELD, OFTString);
      65          17 :         m_defn->AddFieldDefn(std::move(poDescriptionFieldDefn));
      66             : 
      67          34 :         m_defn->GetGeomFieldDefn(0)->SetSpatialRef(
      68          17 :             m_srcLayer.GetLayerDefn()
      69          17 :                 ->GetGeomFieldDefn(m_srcGeomField)
      70          17 :                 ->GetSpatialRef());
      71          17 :     }
      72             : 
      73             :     ~GDALInvalidLocationLayer() override;
      74             : 
      75           0 :     int TestCapability(const char *) override
      76             :     {
      77           0 :         return false;
      78             :     }
      79             : 
      80           5 :     OGRFeatureDefn *GetLayerDefn() override
      81             :     {
      82           5 :         return m_defn;
      83             :     }
      84             : 
      85           6 :     std::unique_ptr<OGRFeature> CreateFeatureFromLastError()
      86             :     {
      87           6 :         auto poErrorFeature = std::make_unique<OGRFeature>(m_defn);
      88             : 
      89          12 :         std::string msg = CPLGetLastErrorMsg();
      90             : 
      91             :         // Trim GEOS exception name
      92           6 :         const auto subMsgPos = msg.find(": ");
      93           6 :         if (subMsgPos != std::string::npos)
      94             :         {
      95           6 :             msg = msg.substr(subMsgPos + strlen(": "));
      96             :         }
      97             : 
      98             :         // Trim newline from end of GEOS exception message
      99           6 :         if (!msg.empty() && msg.back() == '\n')
     100             :         {
     101           3 :             msg.pop_back();
     102             :         }
     103             : 
     104           6 :         poErrorFeature->SetField(ERROR_DESCRIPTION_FIELD, msg.c_str());
     105             : 
     106           6 :         CPLErrorReset();
     107             : 
     108          12 :         return poErrorFeature;
     109             :     }
     110             : 
     111          35 :     void TranslateFeature(
     112             :         std::unique_ptr<OGRFeature> poSrcFeature,
     113             :         std::vector<std::unique_ptr<OGRFeature>> &apoOutputFeatures) override
     114             :     {
     115             :         const OGRGeometry *poGeom =
     116          35 :             poSrcFeature->GetGeomFieldRef(m_srcGeomField);
     117          35 :         std::unique_ptr<OGRFeature> poErrorFeature;
     118             : 
     119          35 :         if (poGeom)
     120             :         {
     121          35 :             if (poGeom->getDimension() < 1)
     122             :             {
     123           1 :                 CPLErrorOnce(CE_Warning, CPLE_AppDefined,
     124             :                              "Point geometry passed to 'gdal vector "
     125             :                              "check-geometry'. Point geometries are "
     126             :                              "always valid/simple.");
     127             :             }
     128             :             else
     129             :             {
     130          34 :                 auto eType = wkbFlatten(poGeom->getGeometryType());
     131          34 :                 GEOSGeometry *poGeosGeom = poGeom->exportToGEOS(m_geosContext);
     132             : 
     133          34 :                 if (!poGeosGeom)
     134             :                 {
     135             :                     // Try to find a useful message / coordinate from
     136             :                     // GEOS exception message.
     137           6 :                     poErrorFeature = CreateFeatureFromLastError();
     138             : 
     139           6 :                     if (eType == wkbPolygon)
     140             :                     {
     141             :                         const OGRLinearRing *poRing =
     142           4 :                             poGeom->toPolygon()->getExteriorRing();
     143           4 :                         if (poRing != nullptr && !poRing->IsEmpty())
     144             :                         {
     145           3 :                             auto poPoint = std::make_unique<OGRPoint>();
     146           3 :                             poRing->StartPoint(poPoint.get());
     147           3 :                             poErrorFeature->SetGeometry(std::move(poPoint));
     148             :                         }
     149             :                         else
     150             :                         {
     151             :                             // TODO get a point from somewhere else?
     152             :                         }
     153             :                     }
     154             :                 }
     155             :                 else
     156             :                 {
     157          28 :                     char *pszReason = nullptr;
     158          28 :                     GEOSGeometry *location = nullptr;
     159          28 :                     char ret = 1;
     160          28 :                     bool warnAboutGeosVersion = false;
     161          28 :                     bool checkedSimple = false;
     162             : 
     163          28 :                     if (eType == wkbPolygon || eType == wkbMultiPolygon ||
     164          10 :                         eType == wkbCurvePolygon || eType == wkbMultiSurface ||
     165             :                         eType == wkbGeometryCollection)
     166             :                     {
     167          20 :                         ret = GEOSisValidDetail_r(m_geosContext, poGeosGeom, 0,
     168             :                                                   &pszReason, &location);
     169             :                     }
     170             : 
     171          28 :                     if (eType == wkbLineString || eType == wkbMultiLineString ||
     172          22 :                         eType == wkbCircularString ||
     173          20 :                         eType == wkbCompoundCurve ||
     174           7 :                         (ret == 1 && eType == wkbGeometryCollection))
     175             :                     {
     176          10 :                         checkedSimple = true;
     177             : #if GEOS_VERSION_MAJOR > 3 ||                                                  \
     178             :     (GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR >= 14)
     179          10 :                         ret = GEOSisSimpleDetail_r(m_geosContext, poGeosGeom, 1,
     180             :                                                    &location);
     181             : #else
     182             :                         ret = GEOSisSimple_r(m_geosContext, poGeosGeom);
     183             :                         warnAboutGeosVersion = true;
     184             : #endif
     185             :                     }
     186             : 
     187          28 :                     GEOSGeom_destroy_r(m_geosContext, poGeosGeom);
     188          28 :                     if (ret == 0)
     189             :                     {
     190          21 :                         if (warnAboutGeosVersion)
     191             :                         {
     192           0 :                             CPLErrorOnce(
     193             :                                 CE_Warning, CPLE_AppDefined,
     194             :                                 "Detected a non-simple linear geometry, but "
     195             :                                 "cannot output self-intersection points "
     196             :                                 "because GEOS library version is < 3.14.");
     197             :                         }
     198             : 
     199          21 :                         poErrorFeature = std::make_unique<OGRFeature>(m_defn);
     200          21 :                         if (pszReason == nullptr)
     201             :                         {
     202           8 :                             if (checkedSimple)
     203             :                             {
     204           8 :                                 poErrorFeature->SetField(
     205             :                                     ERROR_DESCRIPTION_FIELD,
     206             :                                     "self-intersection");
     207             :                             }
     208             :                         }
     209             :                         else
     210             :                         {
     211          13 :                             poErrorFeature->SetField(ERROR_DESCRIPTION_FIELD,
     212             :                                                      pszReason);
     213          13 :                             GEOSFree_r(m_geosContext, pszReason);
     214             :                         }
     215             : 
     216          21 :                         if (location != nullptr)
     217             :                         {
     218             :                             std::unique_ptr<OGRGeometry> poErrorGeom(
     219             :                                 OGRGeometryFactory::createFromGEOS(
     220          21 :                                     m_geosContext, location));
     221          21 :                             GEOSGeom_destroy_r(m_geosContext, location);
     222             : 
     223          42 :                             poErrorGeom->assignSpatialReference(
     224          42 :                                 m_srcLayer.GetLayerDefn()
     225          21 :                                     ->GetGeomFieldDefn(m_srcGeomField)
     226          42 :                                     ->GetSpatialRef());
     227             : 
     228          21 :                             poErrorFeature->SetGeometry(std::move(poErrorGeom));
     229             :                         }
     230             :                     }
     231           7 :                     else if (ret == 2)
     232             :                     {
     233           0 :                         poErrorFeature = CreateFeatureFromLastError();
     234             :                     }
     235             :                 }
     236             :             }
     237             :         }
     238             : 
     239          35 :         if (!poErrorFeature && !m_skipValid)
     240             :         {
     241           2 :             poErrorFeature = std::make_unique<OGRFeature>(m_defn);
     242             :             // TODO Set geometry to POINT EMPTY ?
     243             :         }
     244             : 
     245          35 :         if (poErrorFeature)
     246             :         {
     247          29 :             poErrorFeature->SetFID(poSrcFeature->GetFID());
     248          29 :             apoOutputFeatures.push_back(std::move(poErrorFeature));
     249             :         }
     250          35 :     }
     251             : 
     252             :     CPL_DISALLOW_COPY_ASSIGN(GDALInvalidLocationLayer)
     253             : 
     254             :   private:
     255             :     OGRFeatureDefn *const m_defn;
     256             :     const GEOSContextHandle_t m_geosContext;
     257             :     const int m_srcGeomField;
     258             :     const bool m_skipValid;
     259             : };
     260             : 
     261          34 : GDALInvalidLocationLayer::~GDALInvalidLocationLayer()
     262             : {
     263          17 :     m_defn->Release();
     264          17 :     finishGEOS_r(m_geosContext);
     265          34 : }
     266             : 
     267             : #endif
     268             : 
     269          19 : bool GDALVectorCheckGeometryAlgorithm::RunStep(GDALPipelineStepRunContext &)
     270             : {
     271             : #ifdef HAVE_GEOS
     272          19 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     273          19 :     CPLAssert(poSrcDS);
     274          19 :     CPLAssert(m_outputDataset.GetName().empty());
     275          19 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     276             : 
     277          19 :     const bool bSingleLayerOutput = m_inputLayerNames.empty()
     278          21 :                                         ? poSrcDS->GetLayerCount() == 1
     279           2 :                                         : m_inputLayerNames.size() == 1;
     280             : 
     281          38 :     auto outDS = std::make_unique<GDALVectorPipelineOutputDataset>(*poSrcDS);
     282          37 :     for (auto &&poSrcLayer : poSrcDS->GetLayers())
     283             :     {
     284          22 :         if (m_inputLayerNames.empty() ||
     285           0 :             std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
     286          22 :                       poSrcLayer->GetDescription()) != m_inputLayerNames.end())
     287             :         {
     288          20 :             const auto poSrcLayerDefn = poSrcLayer->GetLayerDefn();
     289          20 :             if (poSrcLayerDefn->GetGeomFieldCount() == 0)
     290             :             {
     291           2 :                 if (m_inputLayerNames.empty())
     292           1 :                     continue;
     293           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     294             :                             "Specified layer '%s' has no geometry field",
     295           1 :                             poSrcLayer->GetDescription());
     296           2 :                 return false;
     297             :             }
     298             : 
     299             :             const int geomFieldIndex =
     300          18 :                 m_geomField.empty()
     301          18 :                     ? 0
     302           2 :                     : poSrcLayerDefn->GetGeomFieldIndex(m_geomField.c_str());
     303             : 
     304          18 :             if (geomFieldIndex == -1)
     305             :             {
     306           1 :                 ReportError(CE_Failure, CPLE_AppDefined,
     307             :                             "Specified geometry field '%s' does not exist in "
     308             :                             "layer '%s'",
     309           1 :                             m_geomField.c_str(), poSrcLayer->GetDescription());
     310           1 :                 return false;
     311             :             }
     312             : 
     313          34 :             outDS->AddLayer(*poSrcLayer,
     314          17 :                             std::make_unique<GDALInvalidLocationLayer>(
     315             :                                 *poSrcLayer, bSingleLayerOutput, geomFieldIndex,
     316          34 :                                 !m_includeValid));
     317             :         }
     318             :     }
     319             : 
     320          17 :     m_outputDataset.Set(std::move(outDS));
     321             : 
     322          17 :     return true;
     323             : #else
     324             :     ReportError(CE_Failure, CPLE_AppDefined,
     325             :                 "%s requires GDAL to be built against the GEOS library.", NAME);
     326             :     return false;
     327             : #endif
     328             : }
     329             : 
     330             : GDALVectorCheckGeometryAlgorithmStandalone::
     331             :     ~GDALVectorCheckGeometryAlgorithmStandalone() = default;
     332             : 
     333             : //! @endcond

Generated by: LCOV version 1.14