/* ****************************************************************************
 *
 * Project:  GDAL Utilities
 * Purpose:  GDAL scattered data gridding (interpolation) tool
 * Author:   Andrey Kiselev, dron@ak4719.spb.edu
 *
 * ****************************************************************************
 * Copyright (c) 2007, Andrey Kiselev <dron@ak4719.spb.edu>
 * Copyright (c) 2015, Even Rouault <even dot rouault at spatialys dot com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_port.h"
#include "gdal_utils.h"
#include "gdal_utils_priv.h"
#include "commonutils.h"
#include "gdalargumentparser.h"

#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <vector>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_progress.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "gdal.h"
#include "gdal_alg.h"
#include "gdal_priv.h"
#include "gdalgrid.h"
#include "ogr_api.h"
#include "ogr_core.h"
#include "ogr_feature.h"
#include "ogr_geometry.h"
#include "ogr_spatialref.h"
#include "ogr_srs_api.h"
#include "ogrsf_frmts.h"

/************************************************************************/
/*                          GDALGridOptions                             */
/************************************************************************/

/** Options for use with GDALGrid(). GDALGridOptions* must be allocated
 * and freed with GDALGridOptionsNew() and GDALGridOptionsFree() respectively.
 */
struct GDALGridOptions
{
    /*! output format. Use the short format name. */
    std::string osFormat{};

    /*! allow or suppress progress monitor and other non-error output */
    bool bQuiet = true;

    /*! the progress function to use */
    GDALProgressFunc pfnProgress = GDALDummyProgress;

    /*! pointer to the progress data variable */
    void *pProgressData = nullptr;

    CPLStringList aosLayers{};
    std::string osBurnAttribute{};
    double dfIncreaseBurnValue = 0.0;
    double dfMultiplyBurnValue = 1.0;
    std::string osWHERE{};
    std::string osSQL{};
    GDALDataType eOutputType = GDT_Float64;
    CPLStringList aosCreateOptions{};
    int nXSize = 0;
    int nYSize = 0;
    double dfXRes = 0;
    double dfYRes = 0;
    double dfXMin = 0;
    double dfXMax = 0;
    double dfYMin = 0;
    double dfYMax = 0;
    bool bIsXExtentSet = false;
    bool bIsYExtentSet = false;
    GDALGridAlgorithm eAlgorithm = GGA_InverseDistanceToAPower;
    std::unique_ptr<void, VSIFreeReleaser> pOptions{};
    std::string osOutputSRS{};
    std::unique_ptr<OGRGeometry> poSpatialFilter{};
    bool bClipSrc = false;
    std::unique_ptr<OGRGeometry> poClipSrc{};
    std::string osClipSrcDS{};
    std::string osClipSrcSQL{};
    std::string osClipSrcLayer{};
    std::string osClipSrcWhere{};
    bool bNoDataSet = false;
    double dfNoDataValue = 0;

    GDALGridOptions()
    {
        void *l_pOptions = nullptr;
        GDALGridParseAlgorithmAndOptions(szAlgNameInvDist, &eAlgorithm,
                                         &l_pOptions);
        pOptions.reset(l_pOptions);
    }

    CPL_DISALLOW_COPY_ASSIGN(GDALGridOptions)
};

/************************************************************************/
/*                          GetAlgorithmName()                          */
/*                                                                      */
/*      Grids algorithm code into mnemonic name.                        */
/************************************************************************/

static void PrintAlgorithmAndOptions(GDALGridAlgorithm eAlgorithm,
                                     void *pOptions)
{
    switch (eAlgorithm)
    {
        case GGA_InverseDistanceToAPower:
        {
            printf("Algorithm name: \"%s\".\n", szAlgNameInvDist);
            GDALGridInverseDistanceToAPowerOptions *pOptions2 =
                static_cast<GDALGridInverseDistanceToAPowerOptions *>(pOptions);
            CPLprintf("Options are "
                      "\"power=%f:smoothing=%f:radius1=%f:radius2=%f:angle=%f"
                      ":max_points=%u:min_points=%u:nodata=%f\"\n",
                      pOptions2->dfPower, pOptions2->dfSmoothing,
                      pOptions2->dfRadius1, pOptions2->dfRadius2,
                      pOptions2->dfAngle, pOptions2->nMaxPoints,
                      pOptions2->nMinPoints, pOptions2->dfNoDataValue);
            break;
        }
        case GGA_InverseDistanceToAPowerNearestNeighbor:
        {
            printf("Algorithm name: \"%s\".\n",
                   szAlgNameInvDistNearestNeighbor);
            GDALGridInverseDistanceToAPowerNearestNeighborOptions *pOptions2 =
                static_cast<
                    GDALGridInverseDistanceToAPowerNearestNeighborOptions *>(
                    pOptions);
            CPLString osStr;
            osStr.Printf("power=%f:smoothing=%f:radius=%f"
                         ":max_points=%u:min_points=%u:nodata=%f",
                         pOptions2->dfPower, pOptions2->dfSmoothing,
                         pOptions2->dfRadius, pOptions2->nMaxPoints,
                         pOptions2->nMinPoints, pOptions2->dfNoDataValue);
            if (pOptions2->nMinPointsPerQuadrant > 0)
                osStr += CPLSPrintf(":min_points_per_quadrant=%u",
                                    pOptions2->nMinPointsPerQuadrant);
            if (pOptions2->nMaxPointsPerQuadrant > 0)
                osStr += CPLSPrintf(":max_points_per_quadrant=%u",
                                    pOptions2->nMaxPointsPerQuadrant);
            printf("Options are: \"%s\n", osStr.c_str()); /* ok */
            break;
        }
        case GGA_MovingAverage:
        {
            printf("Algorithm name: \"%s\".\n", szAlgNameAverage);
            GDALGridMovingAverageOptions *pOptions2 =
                static_cast<GDALGridMovingAverageOptions *>(pOptions);
            CPLString osStr;
            osStr.Printf("radius1=%f:radius2=%f:angle=%f:min_points=%u"
                         ":nodata=%f",
                         pOptions2->dfRadius1, pOptions2->dfRadius2,
                         pOptions2->dfAngle, pOptions2->nMinPoints,
                         pOptions2->dfNoDataValue);
            if (pOptions2->nMinPointsPerQuadrant > 0)
                osStr += CPLSPrintf(":min_points_per_quadrant=%u",
                                    pOptions2->nMinPointsPerQuadrant);
            if (pOptions2->nMaxPointsPerQuadrant > 0)
                osStr += CPLSPrintf(":max_points_per_quadrant=%u",
                                    pOptions2->nMaxPointsPerQuadrant);
            if (pOptions2->nMaxPoints > 0)
                osStr += CPLSPrintf(":max_points=%u", pOptions2->nMaxPoints);
            printf("Options are: \"%s\n", osStr.c_str()); /* ok */
            break;
        }
        case GGA_NearestNeighbor:
        {
            printf("Algorithm name: \"%s\".\n", szAlgNameNearest);
            GDALGridNearestNeighborOptions *pOptions2 =
                static_cast<GDALGridNearestNeighborOptions *>(pOptions);
            CPLprintf("Options are "
                      "\"radius1=%f:radius2=%f:angle=%f:nodata=%f\"\n",
                      pOptions2->dfRadius1, pOptions2->dfRadius2,
                      pOptions2->dfAngle, pOptions2->dfNoDataValue);
            break;
        }
        case GGA_MetricMinimum:
        case GGA_MetricMaximum:
        case GGA_MetricRange:
        case GGA_MetricCount:
        case GGA_MetricAverageDistance:
        case GGA_MetricAverageDistancePts:
        {
            const char *pszAlgName = "";
            CPL_IGNORE_RET_VAL(pszAlgName);  // Make CSA happy
            switch (eAlgorithm)
            {
                case GGA_MetricMinimum:
                    pszAlgName = szAlgNameMinimum;
                    break;
                case GGA_MetricMaximum:
                    pszAlgName = szAlgNameMaximum;
                    break;
                case GGA_MetricRange:
                    pszAlgName = szAlgNameRange;
                    break;
                case GGA_MetricCount:
                    pszAlgName = szAlgNameCount;
                    break;
                case GGA_MetricAverageDistance:
                    pszAlgName = szAlgNameAverageDistance;
                    break;
                case GGA_MetricAverageDistancePts:
                    pszAlgName = szAlgNameAverageDistancePts;
                    break;
                default:
                    CPLAssert(false);
                    break;
            }
            printf("Algorithm name: \"%s\".\n", pszAlgName);
            GDALGridDataMetricsOptions *pOptions2 =
                static_cast<GDALGridDataMetricsOptions *>(pOptions);
            CPLString osStr;
            osStr.Printf("radius1=%f:radius2=%f:angle=%f:min_points=%u"
                         ":nodata=%f",
                         pOptions2->dfRadius1, pOptions2->dfRadius2,
                         pOptions2->dfAngle, pOptions2->nMinPoints,
                         pOptions2->dfNoDataValue);
            if (pOptions2->nMinPointsPerQuadrant > 0)
                osStr += CPLSPrintf(":min_points_per_quadrant=%u",
                                    pOptions2->nMinPointsPerQuadrant);
            if (pOptions2->nMaxPointsPerQuadrant > 0)
                osStr += CPLSPrintf(":max_points_per_quadrant=%u",
                                    pOptions2->nMaxPointsPerQuadrant);
            printf("Options are: \"%s\n", osStr.c_str()); /* ok */
            break;
        }
        case GGA_Linear:
        {
            printf("Algorithm name: \"%s\".\n", szAlgNameLinear);
            GDALGridLinearOptions *pOptions2 =
                static_cast<GDALGridLinearOptions *>(pOptions);
            CPLprintf("Options are "
                      "\"radius=%f:nodata=%f\"\n",
                      pOptions2->dfRadius, pOptions2->dfNoDataValue);
            break;
        }
        default:
        {
            printf("Algorithm is unknown.\n");
            break;
        }
    }
}

/************************************************************************/
/*  Extract point coordinates from the geometry reference and set the   */
/*  Z value as requested. Test whether we are in the clipped region     */
/*  before processing.                                                  */
/************************************************************************/

class GDALGridGeometryVisitor final : public OGRDefaultConstGeometryVisitor
{
  public:
    const OGRGeometry *poClipSrc = nullptr;
    int iBurnField = 0;
    double dfBurnValue = 0;
    double dfIncreaseBurnValue = 0;
    double dfMultiplyBurnValue = 1;
    std::vector<double> adfX{};
    std::vector<double> adfY{};
    std::vector<double> adfZ{};

    using OGRDefaultConstGeometryVisitor::visit;

    void visit(const OGRPoint *p) override;
};

void GDALGridGeometryVisitor::visit(const OGRPoint *p)
{
    if (poClipSrc && !p->Within(poClipSrc))
        return;

    if (iBurnField < 0 && std::isnan(p->getZ()))
        return;

    adfX.push_back(p->getX());
    adfY.push_back(p->getY());
    if (iBurnField < 0)
        adfZ.push_back((p->getZ() + dfIncreaseBurnValue) * dfMultiplyBurnValue);
    else
        adfZ.push_back((dfBurnValue + dfIncreaseBurnValue) *
                       dfMultiplyBurnValue);
}

/************************************************************************/
/*                            ProcessLayer()                            */
/*                                                                      */
/*      Process all the features in a layer selection, collecting       */
/*      geometries and burn values.                                     */
/************************************************************************/

static CPLErr ProcessLayer(OGRLayer *poSrcLayer, GDALDataset *poDstDS,
                           const OGRGeometry *poClipSrc, int nXSize, int nYSize,
                           int nBand, bool &bIsXExtentSet, bool &bIsYExtentSet,
                           double &dfXMin, double &dfXMax, double &dfYMin,
                           double &dfYMax, const std::string &osBurnAttribute,
                           const double dfIncreaseBurnValue,
                           const double dfMultiplyBurnValue, GDALDataType eType,
                           GDALGridAlgorithm eAlgorithm, void *pOptions,
                           bool bQuiet, GDALProgressFunc pfnProgress,
                           void *pProgressData)

{
    /* -------------------------------------------------------------------- */
    /*      Get field index, and check.                                     */
    /* -------------------------------------------------------------------- */
    int iBurnField = -1;

    if (!osBurnAttribute.empty())
    {
        iBurnField =
            poSrcLayer->GetLayerDefn()->GetFieldIndex(osBurnAttribute.c_str());
        if (iBurnField == -1)
        {
            printf("Failed to find field %s on layer %s, skipping.\n",
                   osBurnAttribute.c_str(), poSrcLayer->GetName());
            return CE_Failure;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Collect the geometries from this layer, and build list of       */
    /*      values to be interpolated.                                      */
    /* -------------------------------------------------------------------- */
    GDALGridGeometryVisitor oVisitor;
    oVisitor.poClipSrc = poClipSrc;
    oVisitor.iBurnField = iBurnField;
    oVisitor.dfIncreaseBurnValue = dfIncreaseBurnValue;
    oVisitor.dfMultiplyBurnValue = dfMultiplyBurnValue;

    for (auto &&poFeat : poSrcLayer)
    {
        const OGRGeometry *poGeom = poFeat->GetGeometryRef();
        if (poGeom)
        {
            if (iBurnField >= 0)
            {
                if (!poFeat->IsFieldSetAndNotNull(iBurnField))
                {
                    continue;
                }
                oVisitor.dfBurnValue = poFeat->GetFieldAsDouble(iBurnField);
            }

            poGeom->accept(&oVisitor);
        }
    }

    if (oVisitor.adfX.empty())
    {
        printf("No point geometry found on layer %s, skipping.\n",
               poSrcLayer->GetName());
        return CE_None;
    }

    /* -------------------------------------------------------------------- */
    /*      Compute grid geometry.                                          */
    /* -------------------------------------------------------------------- */
    if (!bIsXExtentSet || !bIsYExtentSet)
    {
        OGREnvelope sEnvelope;
        if (poSrcLayer->GetExtent(&sEnvelope, TRUE) == OGRERR_FAILURE)
        {
            return CE_Failure;
        }

        if (!bIsXExtentSet)
        {
            dfXMin = sEnvelope.MinX;
            dfXMax = sEnvelope.MaxX;
            bIsXExtentSet = true;
        }

        if (!bIsYExtentSet)
        {
            dfYMin = sEnvelope.MinY;
            dfYMax = sEnvelope.MaxY;
            bIsYExtentSet = true;
        }
    }

    // Produce north-up images
    if (dfYMin < dfYMax)
        std::swap(dfYMin, dfYMax);

    /* -------------------------------------------------------------------- */
    /*      Perform gridding.                                               */
    /* -------------------------------------------------------------------- */

    const double dfDeltaX = (dfXMax - dfXMin) / nXSize;
    const double dfDeltaY = (dfYMax - dfYMin) / nYSize;

    if (!bQuiet)
    {
        printf("Grid data type is \"%s\"\n", GDALGetDataTypeName(eType));
        printf("Grid size = (%d %d).\n", nXSize, nYSize);
        CPLprintf("Corner coordinates = (%f %f)-(%f %f).\n", dfXMin, dfYMin,
                  dfXMax, dfYMax);
        CPLprintf("Grid cell size = (%f %f).\n", dfDeltaX, dfDeltaY);
        printf("Source point count = %lu.\n",
               static_cast<unsigned long>(oVisitor.adfX.size()));
        PrintAlgorithmAndOptions(eAlgorithm, pOptions);
        printf("\n");
    }

    GDALRasterBand *poBand = poDstDS->GetRasterBand(nBand);

    int nBlockXSize = 0;
    int nBlockYSize = 0;
    const int nDataTypeSize = GDALGetDataTypeSizeBytes(eType);

    // Try to grow the work buffer up to 16 MB if it is smaller
    poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    if (nXSize == 0 || nYSize == 0 || nBlockXSize == 0 || nBlockYSize == 0)
        return CE_Failure;

    const int nDesiredBufferSize = 16 * 1024 * 1024;
    if (nBlockXSize < nXSize && nBlockYSize < nYSize &&
        nBlockXSize < nDesiredBufferSize / (nBlockYSize * nDataTypeSize))
    {
        const int nNewBlockXSize =
            nDesiredBufferSize / (nBlockYSize * nDataTypeSize);
        nBlockXSize = (nNewBlockXSize / nBlockXSize) * nBlockXSize;
        if (nBlockXSize > nXSize)
            nBlockXSize = nXSize;
    }
    else if (nBlockXSize == nXSize && nBlockYSize < nYSize &&
             nBlockYSize < nDesiredBufferSize / (nXSize * nDataTypeSize))
    {
        const int nNewBlockYSize =
            nDesiredBufferSize / (nXSize * nDataTypeSize);
        nBlockYSize = (nNewBlockYSize / nBlockYSize) * nBlockYSize;
        if (nBlockYSize > nYSize)
            nBlockYSize = nYSize;
    }
    CPLDebug("GDAL_GRID", "Work buffer: %d * %d", nBlockXSize, nBlockYSize);

    std::unique_ptr<void, VSIFreeReleaser> pData(
        VSIMalloc3(nBlockXSize, nBlockYSize, nDataTypeSize));
    if (!pData)
    {
        CPLError(CE_Failure, CPLE_OutOfMemory, "Cannot allocate work buffer");
        return CE_Failure;
    }

    GIntBig nBlock = 0;
    const double dfBlockCount =
        static_cast<double>(DIV_ROUND_UP(nXSize, nBlockXSize)) *
        DIV_ROUND_UP(nYSize, nBlockYSize);

    struct GDALGridContextReleaser
    {
        void operator()(GDALGridContext *psContext)
        {
            GDALGridContextFree(psContext);
        }
    };

    std::unique_ptr<GDALGridContext, GDALGridContextReleaser> psContext(
        GDALGridContextCreate(eAlgorithm, pOptions,
                              static_cast<int>(oVisitor.adfX.size()),
                              &(oVisitor.adfX[0]), &(oVisitor.adfY[0]),
                              &(oVisitor.adfZ[0]), TRUE));
    if (!psContext)
    {
        return CE_Failure;
    }

    CPLErr eErr = CE_None;
    for (int nYOffset = 0; nYOffset < nYSize && eErr == CE_None;
         nYOffset += nBlockYSize)
    {
        for (int nXOffset = 0; nXOffset < nXSize && eErr == CE_None;
             nXOffset += nBlockXSize)
        {
            std::unique_ptr<void, GDALScaledProgressReleaser> pScaledProgress(
                GDALCreateScaledProgress(
                    static_cast<double>(nBlock) / dfBlockCount,
                    static_cast<double>(nBlock + 1) / dfBlockCount, pfnProgress,
                    pProgressData));
            nBlock++;

            int nXRequest = nBlockXSize;
            if (nXOffset > nXSize - nXRequest)
                nXRequest = nXSize - nXOffset;

            int nYRequest = nBlockYSize;
            if (nYOffset > nYSize - nYRequest)
                nYRequest = nYSize - nYOffset;

            eErr = GDALGridContextProcess(
                psContext.get(), dfXMin + dfDeltaX * nXOffset,
                dfXMin + dfDeltaX * (nXOffset + nXRequest),
                dfYMin + dfDeltaY * nYOffset,
                dfYMin + dfDeltaY * (nYOffset + nYRequest), nXRequest,
                nYRequest, eType, pData.get(), GDALScaledProgress,
                pScaledProgress.get());

            if (eErr == CE_None)
                eErr = poBand->RasterIO(GF_Write, nXOffset, nYOffset, nXRequest,
                                        nYRequest, pData.get(), nXRequest,
                                        nYRequest, eType, 0, 0, nullptr);
        }
    }
    if (eErr == CE_None && pfnProgress)
        pfnProgress(1.0, "", pProgressData);

    return eErr;
}

/************************************************************************/
/*                            LoadGeometry()                            */
/*                                                                      */
/*  Read geometries from the given dataset using specified filters and  */
/*  returns a collection of read geometries.                            */
/************************************************************************/

static std::unique_ptr<OGRGeometry> LoadGeometry(const std::string &osDS,
                                                 const std::string &osSQL,
                                                 const std::string &osLyr,
                                                 const std::string &osWhere)
{
    auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
        osDS.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr));
    if (!poDS)
        return nullptr;

    OGRLayer *poLyr = nullptr;
    if (!osSQL.empty())
        poLyr = poDS->ExecuteSQL(osSQL.c_str(), nullptr, nullptr);
    else if (!osLyr.empty())
        poLyr = poDS->GetLayerByName(osLyr.c_str());
    else
        poLyr = poDS->GetLayer(0);

    if (poLyr == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Failed to identify source layer from datasource.");
        return nullptr;
    }

    if (!osWhere.empty())
        poLyr->SetAttributeFilter(osWhere.c_str());

    std::unique_ptr<OGRGeometryCollection> poGeom;
    for (auto &poFeat : poLyr)
    {
        const OGRGeometry *poSrcGeom = poFeat->GetGeometryRef();
        if (poSrcGeom)
        {
            const OGRwkbGeometryType eType =
                wkbFlatten(poSrcGeom->getGeometryType());

            if (!poGeom)
                poGeom = std::make_unique<OGRMultiPolygon>();

            if (eType == wkbPolygon)
            {
                poGeom->addGeometry(poSrcGeom);
            }
            else if (eType == wkbMultiPolygon)
            {
                const int nGeomCount =
                    poSrcGeom->toMultiPolygon()->getNumGeometries();

                for (int iGeom = 0; iGeom < nGeomCount; iGeom++)
                {
                    poGeom->addGeometry(
                        poSrcGeom->toMultiPolygon()->getGeometryRef(iGeom));
                }
            }
            else
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Geometry not of polygon type.");
                if (!osSQL.empty())
                    poDS->ReleaseResultSet(poLyr);
                return nullptr;
            }
        }
    }

    if (!osSQL.empty())
        poDS->ReleaseResultSet(poLyr);

    return poGeom;
}

/************************************************************************/
/*                               GDALGrid()                             */
/************************************************************************/

/* clang-format off */
/**
 * Create raster from the scattered data.
 *
 * This is the equivalent of the
 * <a href="/programs/gdal_grid.html">gdal_grid</a> utility.
 *
 * GDALGridOptions* must be allocated and freed with GDALGridOptionsNew()
 * and GDALGridOptionsFree() respectively.
 *
 * @param pszDest the destination dataset path.
 * @param hSrcDataset the source dataset handle.
 * @param psOptionsIn the options struct returned by GDALGridOptionsNew() or
 * NULL.
 * @param pbUsageError pointer to a integer output variable to store if any
 * usage error has occurred or NULL.
 * @return the output dataset (new dataset that must be closed using
 * GDALClose()) or NULL in case of error.
 *
 * @since GDAL 2.1
 */
/* clang-format on */

GDALDatasetH GDALGrid(const char *pszDest, GDALDatasetH hSrcDataset,
                      const GDALGridOptions *psOptionsIn, int *pbUsageError)

{
    if (hSrcDataset == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "No source dataset specified.");

        if (pbUsageError)
            *pbUsageError = TRUE;
        return nullptr;
    }
    if (pszDest == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "No target dataset specified.");

        if (pbUsageError)
            *pbUsageError = TRUE;
        return nullptr;
    }

    std::unique_ptr<GDALGridOptions> psOptionsToFree;
    const GDALGridOptions *psOptions = psOptionsIn;
    if (psOptions == nullptr)
    {
        psOptionsToFree = std::make_unique<GDALGridOptions>();
        psOptions = psOptionsToFree.get();
    }

    GDALDataset *poSrcDS = GDALDataset::FromHandle(hSrcDataset);

    if (psOptions->osSQL.empty() && psOptions->aosLayers.empty() &&
        poSrcDS->GetLayerCount() != 1)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Neither -sql nor -l are specified, but the source dataset "
                 "has not one single layer.");
        if (pbUsageError)
            *pbUsageError = TRUE;
        return nullptr;
    }

    if ((psOptions->nXSize != 0 || psOptions->nYSize != 0) &&
        (psOptions->dfXRes != 0 || psOptions->dfYRes != 0))
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "-outsize and -tr options cannot be used at the same time.");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Find the output driver.                                         */
    /* -------------------------------------------------------------------- */
    std::string osFormat;
    if (psOptions->osFormat.empty())
    {
        osFormat = GetOutputDriverForRaster(pszDest);
        if (osFormat.empty())
        {
            return nullptr;
        }
    }
    else
    {
        osFormat = psOptions->osFormat;
    }

    GDALDriverH hDriver = GDALGetDriverByName(osFormat.c_str());
    if (hDriver == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Output driver `%s' not recognised.", osFormat.c_str());
        fprintf(stderr, "The following format drivers are enabled and "
                        "support writing:\n");
        for (int iDr = 0; iDr < GDALGetDriverCount(); iDr++)
        {
            hDriver = GDALGetDriver(iDr);

            if (GDALGetMetadataItem(hDriver, GDAL_DCAP_RASTER, nullptr) !=
                    nullptr &&
                (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) !=
                     nullptr ||
                 GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) !=
                     nullptr))
            {
                fprintf(stderr, "  %s: %s\n", GDALGetDriverShortName(hDriver),
                        GDALGetDriverLongName(hDriver));
            }
        }
        printf("\n");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Create target raster file.                                      */
    /* -------------------------------------------------------------------- */
    int nLayerCount = psOptions->aosLayers.size();
    if (nLayerCount == 0 && psOptions->osSQL.empty())
        nLayerCount = 1; /* due to above check */

    int nBands = nLayerCount;

    if (!psOptions->osSQL.empty())
        nBands++;

    int nXSize;
    int nYSize;
    if (psOptions->dfXRes != 0 && psOptions->dfYRes != 0)
    {
        double dfXSize = (std::fabs(psOptions->dfXMax - psOptions->dfXMin) +
                          (psOptions->dfXRes / 2.0)) /
                         psOptions->dfXRes;
        double dfYSize = (std::fabs(psOptions->dfYMax - psOptions->dfYMin) +
                          (psOptions->dfYRes / 2.0)) /
                         psOptions->dfYRes;

        if (dfXSize >= 1 && dfXSize <= INT_MAX && dfYSize >= 1 &&
            dfYSize <= INT_MAX)
        {
            nXSize = static_cast<int>(dfXSize);
            nYSize = static_cast<int>(dfYSize);
        }
        else
        {
            CPLError(
                CE_Failure, CPLE_IllegalArg,
                "Invalid output size detected. Please check your -tr argument");

            if (pbUsageError)
                *pbUsageError = TRUE;
            return nullptr;
        }
    }
    else
    {
        // FIXME
        nXSize = psOptions->nXSize;
        if (nXSize == 0)
            nXSize = 256;
        nYSize = psOptions->nYSize;
        if (nYSize == 0)
            nYSize = 256;
    }

    std::unique_ptr<GDALDataset> poDstDS(GDALDataset::FromHandle(GDALCreate(
        hDriver, pszDest, nXSize, nYSize, nBands, psOptions->eOutputType,
        psOptions->aosCreateOptions.List())));
    if (!poDstDS)
    {
        return nullptr;
    }

    if (psOptions->bNoDataSet)
    {
        for (int i = 1; i <= nBands; i++)
        {
            poDstDS->GetRasterBand(i)->SetNoDataValue(psOptions->dfNoDataValue);
        }
    }

    double dfXMin = psOptions->dfXMin;
    double dfYMin = psOptions->dfYMin;
    double dfXMax = psOptions->dfXMax;
    double dfYMax = psOptions->dfYMax;
    bool bIsXExtentSet = psOptions->bIsXExtentSet;
    bool bIsYExtentSet = psOptions->bIsYExtentSet;
    CPLErr eErr = CE_None;

    /* -------------------------------------------------------------------- */
    /*      Process SQL request.                                            */
    /* -------------------------------------------------------------------- */

    if (!psOptions->osSQL.empty())
    {
        OGRLayer *poLayer =
            poSrcDS->ExecuteSQL(psOptions->osSQL.c_str(),
                                psOptions->poSpatialFilter.get(), nullptr);
        if (poLayer == nullptr)
        {
            return nullptr;
        }

        // Custom layer will be rasterized in the first band.
        eErr = ProcessLayer(
            poLayer, poDstDS.get(), psOptions->poSpatialFilter.get(), nXSize,
            nYSize, 1, bIsXExtentSet, bIsYExtentSet, dfXMin, dfXMax, dfYMin,
            dfYMax, psOptions->osBurnAttribute, psOptions->dfIncreaseBurnValue,
            psOptions->dfMultiplyBurnValue, psOptions->eOutputType,
            psOptions->eAlgorithm, psOptions->pOptions.get(), psOptions->bQuiet,
            psOptions->pfnProgress, psOptions->pProgressData);

        poSrcDS->ReleaseResultSet(poLayer);
    }

    /* -------------------------------------------------------------------- */
    /*      Process each layer.                                             */
    /* -------------------------------------------------------------------- */
    std::string osOutputSRS(psOptions->osOutputSRS);
    for (int i = 0; i < nLayerCount; i++)
    {
        auto poLayer = psOptions->aosLayers.empty()
                           ? poSrcDS->GetLayer(0)
                           : poSrcDS->GetLayerByName(psOptions->aosLayers[i]);
        if (!poLayer)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Unable to find layer \"%s\".",
                     !psOptions->aosLayers.empty() && psOptions->aosLayers[i]
                         ? psOptions->aosLayers[i]
                         : "null");
            eErr = CE_Failure;
            break;
        }

        if (!psOptions->osWHERE.empty())
        {
            if (poLayer->SetAttributeFilter(psOptions->osWHERE.c_str()) !=
                OGRERR_NONE)
            {
                eErr = CE_Failure;
                break;
            }
        }

        if (psOptions->poSpatialFilter)
            poLayer->SetSpatialFilter(psOptions->poSpatialFilter.get());

        // Fetch the first meaningful SRS definition
        if (osOutputSRS.empty())
        {
            auto poSRS = poLayer->GetSpatialRef();
            if (poSRS)
                osOutputSRS = poSRS->exportToWkt();
        }

        eErr = ProcessLayer(
            poLayer, poDstDS.get(), psOptions->poSpatialFilter.get(), nXSize,
            nYSize, i + 1 + nBands - nLayerCount, bIsXExtentSet, bIsYExtentSet,
            dfXMin, dfXMax, dfYMin, dfYMax, psOptions->osBurnAttribute,
            psOptions->dfIncreaseBurnValue, psOptions->dfMultiplyBurnValue,
            psOptions->eOutputType, psOptions->eAlgorithm,
            psOptions->pOptions.get(), psOptions->bQuiet,
            psOptions->pfnProgress, psOptions->pProgressData);
        if (eErr != CE_None)
            break;
    }

    /* -------------------------------------------------------------------- */
    /*      Apply geotransformation matrix.                                 */
    /* -------------------------------------------------------------------- */
    poDstDS->SetGeoTransform(
        GDALGeoTransform(dfXMin, (dfXMax - dfXMin) / nXSize, 0.0, dfYMin, 0.0,
                         (dfYMax - dfYMin) / nYSize));

    /* -------------------------------------------------------------------- */
    /*      Apply SRS definition if set.                                    */
    /* -------------------------------------------------------------------- */
    if (!osOutputSRS.empty())
    {
        poDstDS->SetProjection(osOutputSRS.c_str());
    }

    /* -------------------------------------------------------------------- */
    /*      End                                                             */
    /* -------------------------------------------------------------------- */

    if (eErr != CE_None)
    {
        return nullptr;
    }

    return GDALDataset::ToHandle(poDstDS.release());
}

/************************************************************************/
/*                       GDALGridOptionsGetParser()                     */
/************************************************************************/

/*! @cond Doxygen_Suppress */

static std::unique_ptr<GDALArgumentParser>
GDALGridOptionsGetParser(GDALGridOptions *psOptions,
                         GDALGridOptionsForBinary *psOptionsForBinary,
                         int nCountClipSrc)
{
    auto argParser = std::make_unique<GDALArgumentParser>(
        "gdal_grid", /* bForBinary=*/psOptionsForBinary != nullptr);

    argParser->add_description(
        _("Creates a regular grid (raster) from the scattered data read from a "
          "vector datasource."));

    argParser->add_epilog(_(
        "Available algorithms and parameters with their defaults:\n"
        "    Inverse distance to a power (default)\n"
        "        "
        "invdist:power=2.0:smoothing=0.0:radius1=0.0:radius2=0.0:angle=0.0:max_"
        "points=0:min_points=0:nodata=0.0\n"
        "    Inverse distance to a power with nearest neighbor search\n"
        "        "
        "invdistnn:power=2.0:radius=1.0:max_points=12:min_points=0:nodata=0\n"
        "    Moving average\n"
        "        "
        "average:radius1=0.0:radius2=0.0:angle=0.0:min_points=0:nodata=0.0\n"
        "    Nearest neighbor\n"
        "        nearest:radius1=0.0:radius2=0.0:angle=0.0:nodata=0.0\n"
        "    Various data metrics\n"
        "        <metric "
        "name>:radius1=0.0:radius2=0.0:angle=0.0:min_points=0:nodata=0.0\n"
        "        possible metrics are:\n"
        "            minimum\n"
        "            maximum\n"
        "            range\n"
        "            count\n"
        "            average_distance\n"
        "            average_distance_pts\n"
        "    Linear\n"
        "        linear:radius=-1.0:nodata=0.0\n"
        "\n"
        "For more details, consult https://gdal.org/programs/gdal_grid.html"));

    argParser->add_quiet_argument(
        psOptionsForBinary ? &psOptionsForBinary->bQuiet : nullptr);

    argParser->add_output_format_argument(psOptions->osFormat);

    argParser->add_output_type_argument(psOptions->eOutputType);

    argParser->add_argument("-txe")
        .metavar("<xmin> <xmax>")
        .nargs(2)
        .scan<'g', double>()
        .help(_("Set georeferenced X extents of output file to be created."));

    argParser->add_argument("-tye")
        .metavar("<ymin> <ymax>")
        .nargs(2)
        .scan<'g', double>()
        .help(_("Set georeferenced Y extents of output file to be created."));

    argParser->add_argument("-outsize")
        .metavar("<xsize> <ysize>")
        .nargs(2)
        .scan<'i', int>()
        .help(_("Set the size of the output file."));

    argParser->add_argument("-tr")
        .metavar("<xres> <yes>")
        .nargs(2)
        .scan<'g', double>()
        .help(_("Set target resolution."));

    argParser->add_creation_options_argument(psOptions->aosCreateOptions);

    argParser->add_argument("-zfield")
        .metavar("<field_name>")
        .store_into(psOptions->osBurnAttribute)
        .help(_("Field name from which to get Z values."));

    argParser->add_argument("-z_increase")
        .metavar("<increase_value>")
        .store_into(psOptions->dfIncreaseBurnValue)
        .help(_("Addition to the attribute field on the features to be used to "
                "get a Z value from."));

    argParser->add_argument("-z_multiply")
        .metavar("<multiply_value>")
        .store_into(psOptions->dfMultiplyBurnValue)
        .help(_("Multiplication ratio for the Z field.."));

    argParser->add_argument("-where")
        .metavar("<expression>")
        .store_into(psOptions->osWHERE)
        .help(_("Query expression to be applied to select features to process "
                "from the input layer(s)."));

    argParser->add_argument("-l")
        .metavar("<layer_name>")
        .append()
        .action([psOptions](const std::string &s)
                { psOptions->aosLayers.AddString(s.c_str()); })
        .help(_("Layer(s) from the datasource that will be used for input "
                "features."));

    argParser->add_argument("-sql")
        .metavar("<select_statement>")
        .store_into(psOptions->osSQL)
        .help(_("SQL statement to be evaluated to produce a layer of features "
                "to be processed."));

    argParser->add_argument("-spat")
        .metavar("<xmin> <ymin> <xmax> <ymax>")
        .nargs(4)
        .scan<'g', double>()
        .help(_("The area of interest. Only features within the rectangle will "
                "be reported."));

    argParser->add_argument("-clipsrc")
        .nargs(nCountClipSrc)
        .metavar("[<xmin> <ymin> <xmax> <ymax>]|<WKT>|<datasource>|spat_extent")
        .help(_("Clip geometries (in source SRS)."));

    argParser->add_argument("-clipsrcsql")
        .metavar("<sql_statement>")
        .store_into(psOptions->osClipSrcSQL)
        .help(_("Select desired geometries from the source clip datasource "
                "using an SQL query."));

    argParser->add_argument("-clipsrclayer")
        .metavar("<layername>")
        .store_into(psOptions->osClipSrcLayer)
        .help(_("Select the named layer from the source clip datasource."));

    argParser->add_argument("-clipsrcwhere")
        .metavar("<expression>")
        .store_into(psOptions->osClipSrcWhere)
        .help(_("Restrict desired geometries from the source clip layer based "
                "on an attribute query."));

    argParser->add_argument("-a_srs")
        .metavar("<srs_def>")
        .action(
            [psOptions](const std::string &osOutputSRSDef)
            {
                OGRSpatialReference oOutputSRS;

                if (oOutputSRS.SetFromUserInput(osOutputSRSDef.c_str()) !=
                    OGRERR_NONE)
                {
                    throw std::invalid_argument(
                        std::string("Failed to process SRS definition: ")
                            .append(osOutputSRSDef));
                }

                char *pszWKT = nullptr;
                oOutputSRS.exportToWkt(&pszWKT);
                if (pszWKT)
                    psOptions->osOutputSRS = pszWKT;
                CPLFree(pszWKT);
            })
        .help(_("Assign an output SRS, but without reprojecting."));

    argParser->add_argument("-a")
        .metavar("<algorithm>[[:<parameter1>=<value1>]...]")
        .action(
            [psOptions](const std::string &s)
            {
                const char *pszAlgorithm = s.c_str();
                void *pOptions = nullptr;
                if (GDALGridParseAlgorithmAndOptions(pszAlgorithm,
                                                     &psOptions->eAlgorithm,
                                                     &pOptions) != CE_None)
                {
                    throw std::invalid_argument(
                        "Failed to process algorithm name and parameters");
                }
                psOptions->pOptions.reset(pOptions);

                const CPLStringList aosParams(
                    CSLTokenizeString2(pszAlgorithm, ":", FALSE));
                const char *pszNoDataValue = aosParams.FetchNameValue("nodata");
                if (pszNoDataValue != nullptr)
                {
                    psOptions->bNoDataSet = true;
                    psOptions->dfNoDataValue = CPLAtofM(pszNoDataValue);
                }
            })
        .help(_("Set the interpolation algorithm or data metric name and "
                "(optionally) its parameters."));

    if (psOptionsForBinary)
    {
        argParser->add_open_options_argument(
            &(psOptionsForBinary->aosOpenOptions));
    }

    if (psOptionsForBinary)
    {
        argParser->add_argument("src_dataset_name")
            .metavar("<src_dataset_name>")
            .store_into(psOptionsForBinary->osSource)
            .help(_("Input dataset."));

        argParser->add_argument("dst_dataset_name")
            .metavar("<dst_dataset_name>")
            .store_into(psOptionsForBinary->osDest)
            .help(_("Output dataset."));
    }

    return argParser;
}

/*! @endcond */

/************************************************************************/
/*                         GDALGridGetParserUsage()                     */
/************************************************************************/

std::string GDALGridGetParserUsage()
{
    try
    {
        GDALGridOptions sOptions;
        GDALGridOptionsForBinary sOptionsForBinary;
        auto argParser =
            GDALGridOptionsGetParser(&sOptions, &sOptionsForBinary, 1);
        return argParser->usage();
    }
    catch (const std::exception &err)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
                 err.what());
        return std::string();
    }
}

/************************************************************************/
/*                   CHECK_HAS_ENOUGH_ADDITIONAL_ARGS()                 */
/************************************************************************/

#ifndef CheckHasEnoughAdditionalArgs_defined
#define CheckHasEnoughAdditionalArgs_defined

static bool CheckHasEnoughAdditionalArgs(CSLConstList papszArgv, int i,
                                         int nExtraArg, int nArgc)
{
    if (i + nExtraArg >= nArgc)
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "%s option requires %d argument%s", papszArgv[i], nExtraArg,
                 nExtraArg == 1 ? "" : "s");
        return false;
    }
    return true;
}
#endif

#define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg)                            \
    if (!CheckHasEnoughAdditionalArgs(papszArgv, i, nExtraArg, nArgc))         \
    {                                                                          \
        return nullptr;                                                        \
    }

/************************************************************************/
/*                             GDALGridOptionsNew()                     */
/************************************************************************/

/**
 * Allocates a GDALGridOptions struct.
 *
 * @param papszArgv NULL terminated list of options (potentially including
 * filename and open options too), or NULL. The accepted options are the ones of
 * the <a href="/programs/gdal_translate.html">gdal_translate</a> utility.
 * @param psOptionsForBinary (output) may be NULL (and should generally be
 * NULL), otherwise (gdal_translate_bin.cpp use case) must be allocated with
 *                           GDALGridOptionsForBinaryNew() prior to this
 * function. Will be filled with potentially present filename, open options,...
 * @return pointer to the allocated GDALGridOptions struct. Must be freed with
 * GDALGridOptionsFree().
 *
 * @since GDAL 2.1
 */

GDALGridOptions *
GDALGridOptionsNew(char **papszArgv,
                   GDALGridOptionsForBinary *psOptionsForBinary)
{
    auto psOptions = std::make_unique<GDALGridOptions>();

    /* -------------------------------------------------------------------- */
    /*      Pre-processing for custom syntax that ArgumentParser does not   */
    /*      support.                                                        */
    /* -------------------------------------------------------------------- */

    CPLStringList aosArgv;
    const int nArgc = CSLCount(papszArgv);
    int nCountClipSrc = 0;
    for (int i = 0;
         i < nArgc && papszArgv != nullptr && papszArgv[i] != nullptr; i++)
    {
        if (EQUAL(papszArgv[i], "-clipsrc"))
        {
            if (nCountClipSrc)
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Duplicate argument %s",
                         papszArgv[i]);
                return nullptr;
            }
            // argparse doesn't handle well variable number of values
            // just before the positional arguments, so we have to detect
            // it manually and set the correct number.
            nCountClipSrc = 1;
            CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
            if (CPLGetValueType(papszArgv[i + 1]) != CPL_VALUE_STRING &&
                i + 4 < nArgc)
            {
                nCountClipSrc = 4;
            }

            for (int j = 0; j < 1 + nCountClipSrc; ++j)
            {
                aosArgv.AddString(papszArgv[i]);
                ++i;
            }
            --i;
        }

        else
        {
            aosArgv.AddString(papszArgv[i]);
        }
    }

    try
    {
        auto argParser = GDALGridOptionsGetParser(
            psOptions.get(), psOptionsForBinary, nCountClipSrc);

        argParser->parse_args_without_binary_name(aosArgv.List());

        if (auto oTXE = argParser->present<std::vector<double>>("-txe"))
        {
            psOptions->dfXMin = (*oTXE)[0];
            psOptions->dfXMax = (*oTXE)[1];
            psOptions->bIsXExtentSet = true;
        }

        if (auto oTYE = argParser->present<std::vector<double>>("-tye"))
        {
            psOptions->dfYMin = (*oTYE)[0];
            psOptions->dfYMax = (*oTYE)[1];
            psOptions->bIsYExtentSet = true;
        }

        if (auto oOutsize = argParser->present<std::vector<int>>("-outsize"))
        {
            psOptions->nXSize = (*oOutsize)[0];
            psOptions->nYSize = (*oOutsize)[1];
        }

        if (auto adfTargetRes = argParser->present<std::vector<double>>("-tr"))
        {
            psOptions->dfXRes = (*adfTargetRes)[0];
            psOptions->dfYRes = (*adfTargetRes)[1];
            if (psOptions->dfXRes <= 0 || psOptions->dfYRes <= 0)
            {
                CPLError(CE_Failure, CPLE_IllegalArg,
                         "Wrong value for -tr parameters.");
                return nullptr;
            }
        }

        if (auto oSpat = argParser->present<std::vector<double>>("-spat"))
        {
            const double dfMinX = (*oSpat)[0];
            const double dfMinY = (*oSpat)[1];
            const double dfMaxX = (*oSpat)[2];
            const double dfMaxY = (*oSpat)[3];

            auto poPolygon =
                std::make_unique<OGRPolygon>(dfMinX, dfMinY, dfMaxX, dfMaxY);
            psOptions->poSpatialFilter = std::move(poPolygon);
        }

        if (auto oClipSrc =
                argParser->present<std::vector<std::string>>("-clipsrc"))
        {
            const std::string &osVal = (*oClipSrc)[0];

            psOptions->poClipSrc.reset();
            psOptions->osClipSrcDS.clear();

            VSIStatBufL sStat;
            psOptions->bClipSrc = true;
            if (oClipSrc->size() == 4)
            {
                const double dfMinX = CPLAtofM((*oClipSrc)[0].c_str());
                const double dfMinY = CPLAtofM((*oClipSrc)[1].c_str());
                const double dfMaxX = CPLAtofM((*oClipSrc)[2].c_str());
                const double dfMaxY = CPLAtofM((*oClipSrc)[3].c_str());

                OGRLinearRing oRing;

                oRing.addPoint(dfMinX, dfMinY);
                oRing.addPoint(dfMinX, dfMaxY);
                oRing.addPoint(dfMaxX, dfMaxY);
                oRing.addPoint(dfMaxX, dfMinY);
                oRing.addPoint(dfMinX, dfMinY);

                auto poPoly = std::make_unique<OGRPolygon>();
                poPoly->addRing(&oRing);
                psOptions->poClipSrc = std::move(poPoly);
            }
            else if ((STARTS_WITH_CI(osVal.c_str(), "POLYGON") ||
                      STARTS_WITH_CI(osVal.c_str(), "MULTIPOLYGON")) &&
                     VSIStatL(osVal.c_str(), &sStat) != 0)
            {
                psOptions->poClipSrc =
                    OGRGeometryFactory::createFromWkt(osVal.c_str(), nullptr)
                        .first;
                if (psOptions->poClipSrc == nullptr)
                {
                    CPLError(CE_Failure, CPLE_IllegalArg,
                             "Invalid geometry. Must be a valid POLYGON or "
                             "MULTIPOLYGON WKT");
                    return nullptr;
                }
            }
            else if (EQUAL(osVal.c_str(), "spat_extent"))
            {
                // Nothing to do
            }
            else
            {
                psOptions->osClipSrcDS = osVal;
            }
        }

        if (psOptions->bClipSrc && !psOptions->osClipSrcDS.empty())
        {
            psOptions->poClipSrc = LoadGeometry(
                psOptions->osClipSrcDS, psOptions->osClipSrcSQL,
                psOptions->osClipSrcLayer, psOptions->osClipSrcWhere);
            if (!psOptions->poClipSrc)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Cannot load source clip geometry.");
                return nullptr;
            }
        }
        else if (psOptions->bClipSrc && !psOptions->poClipSrc &&
                 !psOptions->poSpatialFilter)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "-clipsrc must be used with -spat option or \n"
                     "a bounding box, WKT string or datasource must be "
                     "specified.");
            return nullptr;
        }

        if (psOptions->poSpatialFilter)
        {
            if (psOptions->poClipSrc)
            {
                auto poTemp = std::unique_ptr<OGRGeometry>(
                    psOptions->poSpatialFilter->Intersection(
                        psOptions->poClipSrc.get()));
                if (poTemp)
                {
                    psOptions->poSpatialFilter = std::move(poTemp);
                }

                psOptions->poClipSrc.reset();
            }
        }
        else
        {
            if (psOptions->poClipSrc)
            {
                psOptions->poSpatialFilter = std::move(psOptions->poClipSrc);
            }
        }

        if (psOptions->dfXRes != 0 && psOptions->dfYRes != 0 &&
            !(psOptions->bIsXExtentSet && psOptions->bIsYExtentSet))
        {
            CPLError(CE_Failure, CPLE_IllegalArg,
                     "-txe ad -tye arguments must be provided when "
                     "resolution is provided.");
            return nullptr;
        }

        return psOptions.release();
    }
    catch (const std::exception &err)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what());
        return nullptr;
    }
}

/************************************************************************/
/*                          GDALGridOptionsFree()                       */
/************************************************************************/

/**
 * Frees the GDALGridOptions struct.
 *
 * @param psOptions the options struct for GDALGrid().
 *
 * @since GDAL 2.1
 */

void GDALGridOptionsFree(GDALGridOptions *psOptions)
{
    delete psOptions;
}

/************************************************************************/
/*                     GDALGridOptionsSetProgress()                     */
/************************************************************************/

/**
 * Set a progress function.
 *
 * @param psOptions the options struct for GDALGrid().
 * @param pfnProgress the progress callback.
 * @param pProgressData the user data for the progress callback.
 *
 * @since GDAL 2.1
 */

void GDALGridOptionsSetProgress(GDALGridOptions *psOptions,
                                GDALProgressFunc pfnProgress,
                                void *pProgressData)
{
    psOptions->pfnProgress = pfnProgress;
    psOptions->pProgressData = pProgressData;
    if (pfnProgress == GDALTermProgress)
        psOptions->bQuiet = false;
}

#undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS
