LCOV - code coverage report
Current view: top level - apps - gdalalg_raster_create.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 165 186 88.7 %
Date: 2025-05-15 13:16:46 Functions: 3 3 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster create" subcommand
       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_create.h"
      14             : 
      15             : #include "cpl_conv.h"
      16             : #include "gdal_priv.h"
      17             : #include "gdal_utils.h"
      18             : #include "ogr_spatialref.h"
      19             : 
      20             : //! @cond Doxygen_Suppress
      21             : 
      22             : #ifndef _
      23             : #define _(x) (x)
      24             : #endif
      25             : 
      26             : /************************************************************************/
      27             : /*           GDALRasterCreateAlgorithm::GDALRasterCreateAlgorithm()     */
      28             : /************************************************************************/
      29             : 
      30          43 : GDALRasterCreateAlgorithm::GDALRasterCreateAlgorithm()
      31          43 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      32             : {
      33          43 :     AddProgressArg();
      34          43 :     AddOutputFormatArg(&m_outputFormat)
      35             :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
      36         129 :                          {GDAL_DCAP_RASTER, GDAL_DCAP_CREATE});
      37          43 :     AddOpenOptionsArg(&m_openOptions);
      38          43 :     AddInputFormatsArg(&m_inputFormats)
      39          86 :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES, {GDAL_DCAP_RASTER});
      40          43 :     AddInputDatasetArg(&m_inputDataset, GDAL_OF_RASTER, false).AddAlias("like");
      41          43 :     AddOutputDatasetArg(&m_outputDataset, GDAL_OF_RASTER);
      42          43 :     AddCreationOptionsArg(&m_creationOptions);
      43          43 :     const char *exclusionGroup = "overwrite-append";
      44          43 :     AddOverwriteArg(&m_overwrite).SetMutualExclusionGroup(exclusionGroup);
      45             :     AddArg(GDAL_ARG_NAME_APPEND, 0,
      46          86 :            _("Append as a subdataset to existing output"), &m_append)
      47          43 :         .SetDefault(false)
      48          43 :         .SetMutualExclusionGroup(exclusionGroup);
      49          86 :     AddArg("size", 0, _("Output size in pixels"), &m_size)
      50          43 :         .SetMinCount(2)
      51          43 :         .SetMaxCount(2)
      52          43 :         .SetMinValueIncluded(0)
      53          43 :         .SetRepeatedArgAllowed(false)
      54          43 :         .SetDisplayHintAboutRepetition(false)
      55          43 :         .SetMetaVar("<width>,<height>");
      56          86 :     AddArg("band-count", 0, _("Number of bands"), &m_bandCount)
      57          43 :         .SetDefault(m_bandCount)
      58          43 :         .SetMinValueIncluded(0);
      59          43 :     AddOutputDataTypeArg(&m_type).SetDefault(m_type);
      60             : 
      61          43 :     AddNodataDataTypeArg(&m_nodata, /* noneAllowed = */ true);
      62             : 
      63          43 :     AddArg("burn", 0, _("Burn value"), &m_burnValues);
      64          86 :     AddArg("crs", 0, _("Set CRS"), &m_crs)
      65          86 :         .AddHiddenAlias("a_srs")
      66          43 :         .SetIsCRSArg(/*noneAllowed=*/true);
      67          43 :     AddBBOXArg(&m_bbox);
      68             : 
      69             :     {
      70          86 :         auto &arg = AddArg("metadata", 0, _("Add metadata item"), &m_metadata)
      71          86 :                         .SetMetaVar("<KEY>=<VALUE>")
      72          43 :                         .SetPackedValuesAllowed(false);
      73          12 :         arg.AddValidationAction([this, &arg]()
      74          55 :                                 { return ParseAndValidateKeyValue(arg); });
      75          43 :         arg.AddHiddenAlias("mo");
      76             :     }
      77             :     AddArg("copy-metadata", 0, _("Copy metadata from input dataset"),
      78          43 :            &m_copyMetadata);
      79             :     AddArg("copy-overviews", 0,
      80          43 :            _("Create same overview levels as input dataset"), &m_copyOverviews);
      81          43 : }
      82             : 
      83             : /************************************************************************/
      84             : /*                  GDALRasterCreateAlgorithm::RunImpl()                */
      85             : /************************************************************************/
      86             : 
      87          40 : bool GDALRasterCreateAlgorithm::RunImpl(GDALProgressFunc /* pfnProgress */,
      88             :                                         void * /*pProgressData */)
      89             : {
      90          40 :     CPLAssert(!m_outputDataset.GetDatasetRef());
      91             : 
      92          40 :     if (m_outputFormat.empty())
      93             :     {
      94             :         const auto aosFormats =
      95             :             CPLStringList(GDALGetOutputDriversForDatasetName(
      96           6 :                 m_outputDataset.GetName().c_str(), GDAL_OF_RASTER,
      97             :                 /* bSingleMatch = */ true,
      98           6 :                 /* bWarn = */ true));
      99           6 :         if (aosFormats.size() != 1)
     100             :         {
     101           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     102             :                         "Cannot guess driver for %s",
     103           1 :                         m_outputDataset.GetName().c_str());
     104           1 :             return false;
     105             :         }
     106           5 :         m_outputFormat = aosFormats[0];
     107             :     }
     108             : 
     109          78 :     OGRSpatialReference oSRS;
     110             : 
     111          39 :     double adfGT[6] = {0};
     112          39 :     bool bGTValid = false;
     113             : 
     114          39 :     auto poSrcDS = m_inputDataset.GetDatasetRef();
     115          39 :     if (poSrcDS)
     116             :     {
     117          11 :         if (m_size.empty())
     118             :         {
     119          27 :             m_size = std::vector<int>{poSrcDS->GetRasterXSize(),
     120          18 :                                       poSrcDS->GetRasterYSize()};
     121             :         }
     122             : 
     123          11 :         if (!GetArg("band-count")->IsExplicitlySet())
     124             :         {
     125          10 :             m_bandCount = poSrcDS->GetRasterCount();
     126             :         }
     127             : 
     128          11 :         if (!GetArg("datatype")->IsExplicitlySet())
     129             :         {
     130          10 :             if (m_bandCount > 0)
     131             :             {
     132             :                 m_type = GDALGetDataTypeName(
     133          10 :                     poSrcDS->GetRasterBand(1)->GetRasterDataType());
     134             :             }
     135             :         }
     136             : 
     137          11 :         if (m_crs.empty())
     138             :         {
     139           9 :             if (const auto poSRS = poSrcDS->GetSpatialRef())
     140           9 :                 oSRS = *poSRS;
     141             :         }
     142             : 
     143          11 :         if (m_bbox.empty())
     144             :         {
     145          10 :             bGTValid = poSrcDS->GetGeoTransform(adfGT) == CE_None;
     146             :         }
     147             : 
     148          11 :         if (m_nodata.empty() && m_bandCount > 0)
     149             :         {
     150          10 :             int bNoData = false;
     151             :             const double dfNoData =
     152          10 :                 poSrcDS->GetRasterBand(1)->GetNoDataValue(&bNoData);
     153          10 :             if (bNoData)
     154          10 :                 m_nodata = CPLSPrintf("%.17g", dfNoData);
     155             :         }
     156             :     }
     157             : 
     158          39 :     if (m_size.empty())
     159             :     {
     160           1 :         ReportError(CE_Failure, CPLE_IllegalArg,
     161             :                     "Argument 'size' should be specified, or 'like' dataset "
     162             :                     "should be specified");
     163           1 :         return false;
     164             :     }
     165             : 
     166          52 :     if (!m_burnValues.empty() && m_burnValues.size() != 1 &&
     167          14 :         static_cast<int>(m_burnValues.size()) != m_bandCount)
     168             :     {
     169           2 :         if (m_bandCount == 1)
     170             :         {
     171           1 :             ReportError(CE_Failure, CPLE_IllegalArg,
     172             :                         "One value should be provided for argument "
     173             :                         "'burn', given there is one band");
     174             :         }
     175             :         else
     176             :         {
     177           1 :             ReportError(CE_Failure, CPLE_IllegalArg,
     178             :                         "One or %d values should be provided for argument "
     179             :                         "'burn', given there are %d bands",
     180             :                         m_bandCount, m_bandCount);
     181             :         }
     182           2 :         return false;
     183             :     }
     184             : 
     185             :     auto poDriver =
     186          36 :         GetGDALDriverManager()->GetDriverByName(m_outputFormat.c_str());
     187          36 :     if (!poDriver)
     188             :     {
     189             :         // shouldn't happen given checks done in GDALAlgorithm
     190           0 :         ReportError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s",
     191             :                     m_outputFormat.c_str());
     192           0 :         return false;
     193             :     }
     194             : 
     195          36 :     if (m_append)
     196             :     {
     197           2 :         if (poDriver->GetMetadataItem(GDAL_DCAP_CREATE_SUBDATASETS) == nullptr)
     198             :         {
     199           1 :             ReportError(CE_Failure, CPLE_NotSupported,
     200             :                         "-append option not supported for driver %s",
     201           1 :                         poDriver->GetDescription());
     202           1 :             return false;
     203             :         }
     204           1 :         m_creationOptions.push_back("APPEND_SUBDATASET=YES");
     205             :     }
     206             : 
     207             :     auto poRetDS = std::unique_ptr<GDALDataset>(poDriver->Create(
     208         105 :         m_outputDataset.GetName().c_str(), m_size[0], m_size[1], m_bandCount,
     209             :         GDALGetDataTypeByName(m_type.c_str()),
     210          70 :         CPLStringList(m_creationOptions).List()));
     211          35 :     if (!poRetDS)
     212             :     {
     213           1 :         return false;
     214             :     }
     215             : 
     216          34 :     if (!m_crs.empty() && m_crs != "none" && m_crs != "null")
     217             :     {
     218          13 :         oSRS.SetFromUserInput(m_crs.c_str());
     219          13 :         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     220             :     }
     221             : 
     222          34 :     if (!oSRS.IsEmpty())
     223             :     {
     224          22 :         if (poRetDS->SetSpatialRef(&oSRS) != CE_None)
     225             :         {
     226           0 :             ReportError(CE_Failure, CPLE_AppDefined, "Setting CRS failed");
     227           0 :             return false;
     228             :         }
     229             :     }
     230             : 
     231          34 :     if (!m_bbox.empty())
     232             :     {
     233          13 :         if (poRetDS->GetRasterXSize() == 0 || poRetDS->GetRasterYSize() == 0)
     234             :         {
     235           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     236             :                         "Cannot set extent because one of dataset height or "
     237             :                         "width is null");
     238           0 :             return false;
     239             :         }
     240          13 :         bGTValid = true;
     241          13 :         adfGT[0] = m_bbox[0];
     242          13 :         adfGT[1] = (m_bbox[2] - m_bbox[0]) / poRetDS->GetRasterXSize();
     243          13 :         adfGT[2] = 0;
     244          13 :         adfGT[3] = m_bbox[3];
     245          13 :         adfGT[4] = 0;
     246          13 :         adfGT[5] = -(m_bbox[3] - m_bbox[1]) / poRetDS->GetRasterYSize();
     247             :     }
     248          34 :     if (bGTValid)
     249             :     {
     250          23 :         if (poRetDS->SetGeoTransform(adfGT) != CE_None)
     251             :         {
     252           0 :             ReportError(CE_Failure, CPLE_AppDefined, "Setting extent failed");
     253           0 :             return false;
     254             :         }
     255             :     }
     256             : 
     257          34 :     if (!m_nodata.empty() && !EQUAL(m_nodata.c_str(), "none"))
     258             :     {
     259          68 :         for (int i = 0; i < poRetDS->GetRasterCount(); ++i)
     260             :         {
     261          45 :             bool bCannotBeExactlyRepresented = false;
     262          45 :             if (poRetDS->GetRasterBand(i + 1)->SetNoDataValueAsString(
     263          45 :                     m_nodata.c_str(), &bCannotBeExactlyRepresented) != CE_None)
     264             :             {
     265           1 :                 if (bCannotBeExactlyRepresented)
     266             :                 {
     267           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
     268             :                                 "Setting nodata value failed as it cannot be "
     269             :                                 "represented on its data type");
     270             :                 }
     271             :                 else
     272             :                 {
     273           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     274             :                                 "Setting nodata value failed");
     275             :                 }
     276           1 :                 return false;
     277             :             }
     278             :         }
     279             :     }
     280             : 
     281          33 :     if (m_copyMetadata)
     282             :     {
     283           2 :         if (!poSrcDS)
     284             :         {
     285           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     286             :                         "Argument 'copy-metadata' can only be set when an "
     287             :                         "input dataset is set");
     288           1 :             return false;
     289             :         }
     290             :         {
     291           1 :             const CPLStringList aosDomains(poSrcDS->GetMetadataDomainList());
     292           4 :             for (const char *domain : aosDomains)
     293             :             {
     294           3 :                 if (!EQUAL(domain, "IMAGE_STRUCTURE"))
     295             :                 {
     296           4 :                     if (poRetDS->SetMetadata(poSrcDS->GetMetadata(domain),
     297           4 :                                              domain) != CE_None)
     298             :                     {
     299           0 :                         ReportError(CE_Failure, CPLE_AppDefined,
     300             :                                     "Cannot copy '%s' metadata domain", domain);
     301           0 :                         return false;
     302             :                     }
     303             :                 }
     304             :             }
     305             :         }
     306           3 :         for (int i = 0; i < m_bandCount; ++i)
     307             :         {
     308             :             const CPLStringList aosDomains(
     309           2 :                 poSrcDS->GetRasterBand(i + 1)->GetMetadataDomainList());
     310           3 :             for (const char *domain : aosDomains)
     311             :             {
     312           1 :                 if (!EQUAL(domain, "IMAGE_STRUCTURE"))
     313             :                 {
     314           2 :                     if (poRetDS->GetRasterBand(i + 1)->SetMetadata(
     315           1 :                             poSrcDS->GetRasterBand(i + 1)->GetMetadata(domain),
     316           2 :                             domain) != CE_None)
     317             :                     {
     318           0 :                         ReportError(
     319             :                             CE_Failure, CPLE_AppDefined,
     320             :                             "Cannot copy '%s' metadata domain for band %d",
     321             :                             domain, i + 1);
     322           0 :                         return false;
     323             :                     }
     324             :                 }
     325             :             }
     326             :         }
     327             :     }
     328             : 
     329          64 :     const CPLStringList aosMD(m_metadata);
     330          44 :     for (const auto &[key, value] : cpl::IterateNameValue(aosMD))
     331             :     {
     332          12 :         if (poRetDS->SetMetadataItem(key, value) != CE_None)
     333             :         {
     334           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     335             :                         "SetMetadataItem('%s', '%s') failed", key, value);
     336           0 :             return false;
     337             :         }
     338             :     }
     339             : 
     340          32 :     if (m_copyOverviews && m_bandCount > 0)
     341             :     {
     342           3 :         if (!poSrcDS)
     343             :         {
     344           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     345             :                         "Argument 'copy-overviews' can only be set when an "
     346             :                         "input dataset is set");
     347           2 :             return false;
     348             :         }
     349           3 :         if (poSrcDS->GetRasterXSize() != poRetDS->GetRasterXSize() ||
     350           1 :             poSrcDS->GetRasterYSize() != poRetDS->GetRasterYSize())
     351             :         {
     352           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     353             :                         "Argument 'copy-overviews' can only be set when the "
     354             :                         "input and output datasets have the same dimension");
     355           1 :             return false;
     356             :         }
     357             :         const int nOverviewCount =
     358           1 :             poSrcDS->GetRasterBand(1)->GetOverviewCount();
     359           1 :         std::vector<int> anLevels;
     360           2 :         for (int i = 0; i < nOverviewCount; ++i)
     361             :         {
     362           1 :             const auto poOvrBand = poSrcDS->GetRasterBand(1)->GetOverview(i);
     363           1 :             const int nOvrFactor = GDALComputeOvFactor(
     364             :                 poOvrBand->GetXSize(), poSrcDS->GetRasterXSize(),
     365           1 :                 poOvrBand->GetYSize(), poSrcDS->GetRasterYSize());
     366           1 :             anLevels.push_back(nOvrFactor);
     367             :         }
     368           2 :         if (poRetDS->BuildOverviews(
     369           1 :                 "NONE", nOverviewCount, anLevels.data(),
     370             :                 /* nListBands = */ 0, /* panBandList = */ nullptr,
     371             :                 /* pfnProgress = */ nullptr, /* pProgressData = */ nullptr,
     372           1 :                 /* options = */ nullptr) != CE_None)
     373             :         {
     374           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     375             :                         "Creating overview(s) failed");
     376           0 :             return false;
     377             :         }
     378             :     }
     379             : 
     380          30 :     if (!m_burnValues.empty())
     381             :     {
     382          36 :         for (int i = 0; i < m_bandCount; ++i)
     383             :         {
     384          24 :             const int burnValueIdx = m_burnValues.size() == 1 ? 0 : i;
     385          24 :             const auto poDstBand = poRetDS->GetRasterBand(i + 1);
     386             :             // cppcheck-suppress negativeContainerIndex
     387          24 :             if (poDstBand->Fill(m_burnValues[burnValueIdx]) != CE_None)
     388             :             {
     389           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     390             :                             "Setting burn value failed");
     391           0 :                 return false;
     392             :             }
     393             :         }
     394          12 :         if (poRetDS->FlushCache(false) != CE_None)
     395             :         {
     396           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     397             :                         "Setting burn value failed");
     398           0 :             return false;
     399             :         }
     400             :     }
     401             : 
     402          30 :     m_outputDataset.Set(std::move(poRetDS));
     403             : 
     404          30 :     return true;
     405             : }
     406             : 
     407             : //! @endcond

Generated by: LCOV version 1.14