LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_clip.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 175 175 100.0 %
Date: 2026-09-11 05:09:32 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "clip" step of "raster pipeline", or "gdal raster clip" standalone
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_raster_clip.h"
      14             : 
      15             : #include "gdal_priv.h"
      16             : #include "gdal_utils.h"
      17             : 
      18             : #include <algorithm>
      19             : #include <cmath>
      20             : 
      21             : //! @cond Doxygen_Suppress
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : /************************************************************************/
      28             : /*          GDALRasterClipAlgorithm::GDALRasterClipAlgorithm()          */
      29             : /************************************************************************/
      30             : 
      31         130 : GDALRasterClipAlgorithm::GDALRasterClipAlgorithm(bool standaloneStep)
      32             :     : GDALRasterPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      33         130 :                                       standaloneStep)
      34             : {
      35         130 :     constexpr const char *EXCLUSION_GROUP = "bbox-window-geometry-like";
      36         130 :     AddBBOXArg(&m_bbox, _("Clipping bounding box as xmin,ymin,xmax,ymax"))
      37         130 :         .SetMutualExclusionGroup(EXCLUSION_GROUP);
      38         260 :     AddArg("bbox-crs", 0, _("CRS of clipping bounding box"), &m_bboxCrs)
      39         260 :         .SetIsCRSArg()
      40         130 :         .AddHiddenAlias("bbox_srs");
      41             : 
      42             :     AddArg("window", 0, _("Raster window as col,line,width,height in pixels"),
      43         260 :            &m_window)
      44         130 :         .SetRepeatedArgAllowed(false)
      45         130 :         .SetMinCount(4)
      46         130 :         .SetMaxCount(4)
      47         130 :         .SetDisplayHintAboutRepetition(false)
      48         260 :         .SetMutualExclusionGroup(EXCLUSION_GROUP)
      49             :         .AddValidationAction(
      50          41 :             [this]()
      51             :             {
      52          14 :                 CPLAssert(m_window.size() == 4);
      53          14 :                 if (m_window[2] <= 0 || m_window[3] <= 0)
      54             :                 {
      55           2 :                     CPLError(CE_Failure, CPLE_AppDefined,
      56             :                              "Value of 'window' should be "
      57             :                              "col,line,width,height with "
      58             :                              "width > 0 and height > 0");
      59           2 :                     return false;
      60             :                 }
      61          12 :                 return true;
      62         130 :             });
      63             : 
      64         260 :     AddArg("geometry", 0, _("Clipping geometry (WKT or GeoJSON)"), &m_geometry)
      65         130 :         .SetMutualExclusionGroup(EXCLUSION_GROUP);
      66         260 :     AddArg("geometry-crs", 0, _("CRS of clipping geometry"), &m_geometryCrs)
      67         260 :         .SetIsCRSArg()
      68         130 :         .AddHiddenAlias("geometry_srs");
      69             :     AddArg("like", 0, _("Dataset to use as a template for bounds"),
      70         260 :            &m_likeDataset, GDAL_OF_RASTER | GDAL_OF_VECTOR)
      71         260 :         .SetMetaVar("DATASET")
      72         130 :         .SetMutualExclusionGroup(EXCLUSION_GROUP);
      73             :     AddArg("like-sql", 0, ("SELECT statement to run on the 'like' dataset"),
      74         260 :            &m_likeSQL)
      75         260 :         .SetMetaVar("SELECT-STATEMENT")
      76         130 :         .SetMutualExclusionGroup("sql-where");
      77             :     AddArg("like-layer", 0, ("Name of the layer of the 'like' dataset"),
      78         260 :            &m_likeLayer)
      79         130 :         .SetMetaVar("LAYER-NAME");
      80             :     AddArg("like-where", 0, ("WHERE SQL clause to run on the 'like' dataset"),
      81         260 :            &m_likeWhere)
      82         260 :         .SetMetaVar("WHERE-EXPRESSION")
      83         130 :         .SetMutualExclusionGroup("sql-where");
      84             :     AddArg("only-bbox", 0,
      85             :            _("For 'geometry' and 'like', only consider their bounding box"),
      86         130 :            &m_onlyBBOX);
      87             :     AddArg("allow-bbox-outside-source", 0,
      88             :            _("Allow clipping box to include pixels outside input dataset"),
      89         130 :            &m_allowExtentOutsideSource);
      90             :     // Note: this would be a use case for multiple exclusion groups because it is not
      91             :     //       compatible with "window"
      92             :     AddArg("add-alpha", 0,
      93             :            _("Adds an alpha mask band to the destination when the source "
      94             :              "raster have none."),
      95         130 :            &m_addAlpha);
      96         130 : }
      97             : 
      98             : /************************************************************************/
      99             : /*                  GDALRasterClipAlgorithm::RunStep()                  */
     100             : /************************************************************************/
     101             : 
     102          36 : bool GDALRasterClipAlgorithm::RunStep(GDALPipelineStepRunContext &)
     103             : {
     104          36 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     105          36 :     CPLAssert(poSrcDS);
     106          36 :     CPLAssert(m_outputDataset.GetName().empty());
     107          36 :     CPLAssert(!m_outputDataset.GetDatasetRef());
     108             : 
     109          36 :     if (!m_window.empty())
     110             :     {
     111           4 :         if (m_addAlpha)
     112             :         {
     113           1 :             ReportError(CE_Failure, CPLE_NotSupported,
     114             :                         "'alpha' argument is not supported with 'window'");
     115           1 :             return false;
     116             :         }
     117             : 
     118           3 :         CPLStringList aosOptions;
     119           3 :         aosOptions.AddString("-of");
     120           3 :         aosOptions.AddString("VRT");
     121           3 :         aosOptions.AddString("--invoked-from-gdal-algorithm");
     122             : 
     123           3 :         aosOptions.AddString("-srcwin");
     124           3 :         aosOptions.AddString(CPLSPrintf("%d", m_window[0]));
     125           3 :         aosOptions.AddString(CPLSPrintf("%d", m_window[1]));
     126           3 :         aosOptions.AddString(CPLSPrintf("%d", m_window[2]));
     127           3 :         aosOptions.AddString(CPLSPrintf("%d", m_window[3]));
     128             : 
     129           3 :         if (!m_allowExtentOutsideSource)
     130             :         {
     131             :             // Unless we've specifically allowed the bounding box to extend beyond
     132             :             // the source raster, raise an error.
     133           3 :             aosOptions.AddString("-epo");
     134             :         }
     135             : 
     136             :         GDALTranslateOptions *psOptions =
     137           3 :             GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     138             : 
     139           3 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
     140           3 :         auto poRetDS = GDALDataset::FromHandle(
     141             :             GDALTranslate("", hSrcDS, psOptions, nullptr));
     142           3 :         GDALTranslateOptionsFree(psOptions);
     143             : 
     144           3 :         const bool bOK = poRetDS != nullptr;
     145           3 :         if (bOK)
     146             :         {
     147           3 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     148             :         }
     149             : 
     150           3 :         return bOK;
     151             :     }
     152             : 
     153          32 :     GDALGeoTransform gt;
     154          32 :     if (poSrcDS->GetGeoTransform(gt) != CE_None)
     155             :     {
     156           1 :         ReportError(
     157             :             CE_Failure, CPLE_NotSupported,
     158             :             "Clipping is not supported on a raster without a geotransform");
     159           1 :         return false;
     160             :     }
     161          31 :     if (!gt.IsAxisAligned())
     162             :     {
     163           1 :         ReportError(CE_Failure, CPLE_NotSupported,
     164             :                     "Clipping is not supported on a raster whose geotransform "
     165             :                     "has rotation terms");
     166           1 :         return false;
     167             :     }
     168             : 
     169          60 :     auto [poClipGeom, errMsg] = GetClipGeometry();
     170          30 :     if (!poClipGeom)
     171             :     {
     172           3 :         ReportError(CE_Failure, CPLE_AppDefined, "%s", errMsg.c_str());
     173           3 :         return false;
     174             :     }
     175             : 
     176          27 :     auto poLikeDS = m_likeDataset.GetDatasetRef();
     177          28 :     if (!poClipGeom->getSpatialReference() && poLikeDS &&
     178           1 :         poLikeDS->GetLayerCount() == 0)
     179             :     {
     180           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     181             :                     "Dataset '%s' has no CRS. Its bounds cannot be used.",
     182           1 :                     poLikeDS->GetDescription());
     183           1 :         return false;
     184             :     }
     185             : 
     186          52 :     CPLStringList aosOptions;
     187          26 :     aosOptions.AddString("-of");
     188          26 :     aosOptions.AddString("VRT");
     189          26 :     aosOptions.AddString("--invoked-from-gdal-algorithm");
     190             : 
     191          26 :     OGREnvelope env;
     192          26 :     poClipGeom->getEnvelope(&env);
     193             : 
     194          26 :     if (m_onlyBBOX)
     195             :     {
     196           2 :         auto poPoly = std::make_unique<OGRPolygon>(env);
     197           1 :         poPoly->assignSpatialReference(poClipGeom->getSpatialReference());
     198           1 :         poClipGeom = std::move(poPoly);
     199             :     }
     200             : 
     201          26 :     const bool bBottomUpRaster = gt.yscale > 0;
     202             : 
     203          26 :     if (poClipGeom->IsRectangle() && !m_addAlpha && !bBottomUpRaster)
     204             :     {
     205          13 :         aosOptions.AddString("-projwin");
     206          13 :         aosOptions.AddString(env.MinX);
     207          13 :         aosOptions.AddString(env.MaxY);
     208          13 :         aosOptions.AddString(env.MaxX);
     209          13 :         aosOptions.AddString(env.MinY);
     210             : 
     211          13 :         auto poClipGeomSRS = poClipGeom->getSpatialReference();
     212          13 :         if (poClipGeomSRS)
     213             :         {
     214           4 :             const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
     215           8 :             const std::string osWKT = poClipGeomSRS->exportToWkt(apszOptions);
     216           4 :             aosOptions.AddString("-projwin_srs");
     217           4 :             aosOptions.AddString(osWKT.c_str());
     218             :         }
     219             : 
     220          13 :         if (m_allowExtentOutsideSource)
     221             :         {
     222           3 :             aosOptions.AddString("--no-warn-about-outside-window");
     223             :         }
     224             :         else
     225             :         {
     226             :             // Unless we've specifically allowed the bounding box to extend beyond
     227             :             // the source raster, raise an error.
     228          10 :             aosOptions.AddString("-epo");
     229             :         }
     230             : 
     231             :         GDALTranslateOptions *psOptions =
     232          13 :             GDALTranslateOptionsNew(aosOptions.List(), nullptr);
     233             : 
     234          13 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
     235          13 :         auto poRetDS = GDALDataset::FromHandle(
     236             :             GDALTranslate("", hSrcDS, psOptions, nullptr));
     237          13 :         GDALTranslateOptionsFree(psOptions);
     238             : 
     239          13 :         const bool bOK = poRetDS != nullptr;
     240          13 :         if (bOK)
     241             :         {
     242          11 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     243             :         }
     244             : 
     245          13 :         return bOK;
     246             :     }
     247             :     else
     248             :     {
     249          13 :         if (bBottomUpRaster)
     250             :         {
     251           1 :             gt.yorig += gt.yscale * poSrcDS->GetRasterYSize();
     252           1 :             gt.yscale = -gt.yscale;
     253             :         }
     254             : 
     255             :         {
     256             :             auto poClipGeomInSrcSRS =
     257          26 :                 std::unique_ptr<OGRGeometry>(poClipGeom->clone());
     258          13 :             if (poClipGeom->getSpatialReference() && poSrcDS->GetSpatialRef())
     259           7 :                 poClipGeomInSrcSRS->transformTo(poSrcDS->GetSpatialRef());
     260          13 :             poClipGeomInSrcSRS->getEnvelope(&env);
     261             :         }
     262             : 
     263          13 :         OGREnvelope rasterEnv;
     264          13 :         poSrcDS->GetExtent(&rasterEnv, nullptr);
     265          13 :         if (!m_allowExtentOutsideSource && !rasterEnv.Contains(env))
     266             :         {
     267           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     268             :                         "Clipping geometry is partially or totally outside the "
     269             :                         "extent of the raster. You can set the "
     270             :                         "'allow-bbox-outside-source' argument to proceed.");
     271           1 :             return false;
     272             :         }
     273             : 
     274          12 :         if (m_addAlpha)
     275             :         {
     276           2 :             aosOptions.AddString("-dstalpha");
     277             :         }
     278             : 
     279          12 :         aosOptions.AddString("-cutline");
     280          12 :         aosOptions.AddString(poClipGeom->exportToWkt());
     281             : 
     282          12 :         aosOptions.AddString("-wo");
     283          12 :         aosOptions.AddString("CUTLINE_ALL_TOUCHED=YES");
     284             : 
     285          12 :         auto poClipGeomSRS = poClipGeom->getSpatialReference();
     286          12 :         if (poClipGeomSRS)
     287             :         {
     288           7 :             const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
     289          14 :             const std::string osWKT = poClipGeomSRS->exportToWkt(apszOptions);
     290           7 :             aosOptions.AddString("-cutline_srs");
     291           7 :             aosOptions.AddString(osWKT.c_str());
     292             :         }
     293             : 
     294          12 :         constexpr double REL_EPS_PIXEL = 1e-3;
     295          12 :         const double dfMinX =
     296          12 :             gt.xorig +
     297          12 :             floor((env.MinX - gt.xorig) / gt.xscale + REL_EPS_PIXEL) *
     298          12 :                 gt.xscale;
     299          12 :         const double dfMinY =
     300          12 :             gt.yorig +
     301          12 :             ceil((env.MinY - gt.yorig) / gt.yscale - REL_EPS_PIXEL) * gt.yscale;
     302          12 :         const double dfMaxX =
     303          12 :             gt.xorig +
     304          12 :             ceil((env.MaxX - gt.xorig) / gt.xscale - REL_EPS_PIXEL) * gt.xscale;
     305          12 :         const double dfMaxY =
     306          12 :             gt.yorig +
     307          12 :             floor((env.MaxY - gt.yorig) / gt.yscale + REL_EPS_PIXEL) *
     308          12 :                 gt.yscale;
     309             : 
     310          12 :         aosOptions.AddString("-te");
     311          12 :         aosOptions.AddString(dfMinX);
     312          12 :         aosOptions.AddString(bBottomUpRaster ? dfMaxY : dfMinY);
     313          12 :         aosOptions.AddString(dfMaxX);
     314          12 :         aosOptions.AddString(bBottomUpRaster ? dfMinY : dfMaxY);
     315             : 
     316          12 :         aosOptions.AddString("-tr");
     317          12 :         aosOptions.AddString(gt.xscale);
     318          12 :         aosOptions.AddString(std::fabs(gt.yscale));
     319             : 
     320             :         GDALWarpAppOptions *psOptions =
     321          12 :             GDALWarpAppOptionsNew(aosOptions.List(), nullptr);
     322             : 
     323          12 :         GDALDatasetH hSrcDS = GDALDataset::ToHandle(poSrcDS);
     324          12 :         auto poRetDS = GDALDataset::FromHandle(
     325             :             GDALWarp("", nullptr, 1, &hSrcDS, psOptions, nullptr));
     326          12 :         GDALWarpAppOptionsFree(psOptions);
     327             : 
     328          12 :         const bool bOK = poRetDS != nullptr;
     329          12 :         if (bOK)
     330             :         {
     331          12 :             m_outputDataset.Set(std::unique_ptr<GDALDataset>(poRetDS));
     332             :         }
     333             : 
     334          12 :         return bOK;
     335             :     }
     336             : }
     337             : 
     338             : GDALRasterClipAlgorithmStandalone::~GDALRasterClipAlgorithmStandalone() =
     339             :     default;
     340             : 
     341             : //! @endcond

Generated by: LCOV version 1.14