From 4dc63636726ccd87e75c79b00a2c75a6582f2b2f Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 6 Jan 2025 20:10:34 +0100 Subject: [PATCH 01/14] VRT: avoid artifacts on source boundaries when the VRT has an alpha or mask band, and non-nearest neighbour resampling is involved Unfortunately, we have to give up using source overviews and basically apply the VRT at its full resolution before doing the resampling. --- autotest/gcore/vrt_read.py | 110 +++++++++++++++++++++++++++++ frmts/vrt/vrtsourcedrasterband.cpp | 74 ++++++++++++++++++- 2 files changed, 183 insertions(+), 1 deletion(-) diff --git a/autotest/gcore/vrt_read.py b/autotest/gcore/vrt_read.py index 4f24787e176e..5fb40487e4e3 100755 --- a/autotest/gcore/vrt_read.py +++ b/autotest/gcore/vrt_read.py @@ -2862,3 +2862,113 @@ def test_vrt_read_float32_complex_source_from_cfloat32(): ds = gdal.Open("data/vrt/complex_non_zero_real_zero_imag_as_float32.vrt") assert struct.unpack("f" * 4, ds.ReadRaster()) == (1, 1, 1, 1) + + +############################################################################### + + +def test_vrt_resampling_with_mask_and_overviews(tmp_vsimem): + + filename1 = str(tmp_vsimem / "in1.tif") + ds = gdal.GetDriverByName("GTiff").Create(filename1, 100, 10) + ds.SetGeoTransform([0, 1, 0, 0, 0, -1]) + ds.GetRasterBand(1).WriteRaster(0, 0, 48, 10, b"\xFF" * (48 * 10)) + ds.GetRasterBand(1).WriteRaster(48, 0, 2, 10, b"\xF0" * (2 * 10)) + ds.CreateMaskBand(gdal.GMF_PER_DATASET) + ds.GetRasterBand(1).GetMaskBand().WriteRaster(0, 0, 52, 10, b"\xFF" * (52 * 10)) + ds.BuildOverviews("NEAR", [2]) + ds.GetRasterBand(1).GetOverview(0).Fill( + 127 + ) # to demonstrate we ignore overviews unfortunately + ds = None + + filename2 = str(tmp_vsimem / "in2.tif") + ds = gdal.GetDriverByName("GTiff").Create(filename2, 100, 10) + ds.SetGeoTransform([0, 1, 0, 0, 0, -1]) + ds.GetRasterBand(1).WriteRaster(48, 0, 52, 10, b"\xF0" * (52 * 10)) + ds.CreateMaskBand(gdal.GMF_PER_DATASET) + ds.GetRasterBand(1).GetMaskBand().WriteRaster(48, 0, 52, 10, b"\xFF" * (52 * 10)) + ds.BuildOverviews("NEAR", [2]) + ds.GetRasterBand(1).GetOverview(0).Fill( + 127 + ) # to demonstrate we ignore overviews unfortunately + ds = None + + vrt_filename = str(tmp_vsimem / "test.vrt") + gdal.BuildVRT(vrt_filename, [filename1, filename2], resampleAlg=gdal.GRIORA_Cubic) + + ds = gdal.Open(vrt_filename) + assert ( + ds.ReadRaster(buf_xsize=10, buf_ysize=1) + == b"\xFF\xFF\xFF\xFF\xFC\xF0\xF0\xF0\xF0\xF0" + ) + assert ( + ds.GetRasterBand(1).GetMaskBand().ReadRaster(buf_xsize=10, buf_ysize=1) + == b"\xFF" * 10 + ) + + vrt_filename = str(tmp_vsimem / "test.vrt") + gdal.BuildVRT( + vrt_filename, [filename1, filename2], resampleAlg=gdal.GRIORA_Bilinear + ) + + ds = gdal.Open(vrt_filename) + assert ( + ds.ReadRaster(buf_xsize=10, buf_ysize=1) + == b"\xFF\xFF\xFF\xFF\xFB\xF1\xF0\xF0\xF0\xF0" + ) + assert ( + ds.GetRasterBand(1).GetMaskBand().ReadRaster(buf_xsize=10, buf_ysize=1) + == b"\xFF" * 10 + ) + + +############################################################################### + + +def test_vrt_resampling_with_alpha_and_overviews(tmp_vsimem): + + filename1 = str(tmp_vsimem / "in1.tif") + ds = gdal.GetDriverByName("GTiff").Create( + filename1, 100, 10, 2, options=["ALPHA=YES"] + ) + ds.SetGeoTransform([0, 1, 0, 0, 0, -1]) + ds.GetRasterBand(1).WriteRaster(0, 0, 48, 10, b"\xFF" * (48 * 10)) + ds.GetRasterBand(1).WriteRaster(48, 0, 2, 10, b"\xF0" * (2 * 10)) + ds.GetRasterBand(2).WriteRaster(0, 0, 52, 10, b"\xFF" * (52 * 10)) + ds.BuildOverviews("NEAR", [2]) + ds.GetRasterBand(1).GetOverview(0).Fill( + 127 + ) # to demonstrate we ignore overviews unfortunately + ds = None + + filename2 = str(tmp_vsimem / "in2.tif") + ds = gdal.GetDriverByName("GTiff").Create( + filename2, 100, 10, 2, options=["ALPHA=YES"] + ) + ds.SetGeoTransform([0, 1, 0, 0, 0, -1]) + ds.GetRasterBand(1).WriteRaster(48, 0, 52, 10, b"\xF0" * (52 * 10)) + ds.GetRasterBand(2).WriteRaster(48, 0, 52, 10, b"\xFF" * (52 * 10)) + ds.BuildOverviews("NEAR", [2]) + ds.GetRasterBand(1).GetOverview(0).Fill( + 127 + ) # to demonstrate we ignore overviews unfortunately + ds = None + + vrt_filename = str(tmp_vsimem / "test.vrt") + gdal.BuildVRT(vrt_filename, [filename1, filename2], resampleAlg=gdal.GRIORA_Cubic) + + ds = gdal.Open(vrt_filename) + assert ds.ReadRaster( + buf_xsize=10, buf_ysize=1 + ) == b"\xFF\xFF\xFF\xFF\xFC\xF0\xF0\xF0\xF0\xF0" + (b"\xFF" * 10) + + vrt_filename = str(tmp_vsimem / "test.vrt") + gdal.BuildVRT( + vrt_filename, [filename1, filename2], resampleAlg=gdal.GRIORA_Bilinear + ) + + ds = gdal.Open(vrt_filename) + assert ds.ReadRaster( + buf_xsize=10, buf_ysize=1 + ) == b"\xFF\xFF\xFF\xFF\xFB\xF1\xF0\xF0\xF0\xF0" + (b"\xFF" * 10) diff --git a/frmts/vrt/vrtsourcedrasterband.cpp b/frmts/vrt/vrtsourcedrasterband.cpp index cc3c0e6f6698..05bd3111979c 100644 --- a/frmts/vrt/vrtsourcedrasterband.cpp +++ b/frmts/vrt/vrtsourcedrasterband.cpp @@ -124,6 +124,25 @@ bool VRTSourcedRasterBand::CanIRasterIOBeForwardedToEachSource( GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize, int nBufXSize, int nBufYSize, GDALRasterIOExtraArg *psExtraArg) const { + const auto IsNonNearestInvolved = [this, psExtraArg] + { + if (psExtraArg->eResampleAlg != GRIORA_NearestNeighbour) + { + return true; + } + for (int i = 0; i < nSources; i++) + { + if (papoSources[i]->GetType() == VRTComplexSource::GetTypeStatic()) + { + auto *const poSource = + static_cast(papoSources[i]); + if (!poSource->GetResampling().empty()) + return true; + } + } + return false; + }; + // If resampling with non-nearest neighbour, we need to be careful // if the VRT band exposes a nodata value, but the sources do not have it. // To also avoid edge effects on sources when downsampling, use the @@ -131,7 +150,7 @@ bool VRTSourcedRasterBand::CanIRasterIOBeForwardedToEachSource( // nominal resolution, and then downsampling), but only if none of the // contributing sources have overviews. if (eRWFlag == GF_Read && (nXSize != nBufXSize || nYSize != nBufYSize) && - psExtraArg->eResampleAlg != GRIORA_NearestNeighbour && nSources != 0) + nSources != 0 && IsNonNearestInvolved()) { bool bSourceHasOverviews = false; const bool bIsDownsampling = (nBufXSize < nXSize && nBufYSize < nYSize); @@ -148,6 +167,27 @@ bool VRTSourcedRasterBand::CanIRasterIOBeForwardedToEachSource( VRTSimpleSource *const poSource = static_cast(papoSources[i]); + if (poSource->GetType() == VRTComplexSource::GetTypeStatic()) + { + auto *const poComplexSource = + static_cast(poSource); + if (!poComplexSource->GetResampling().empty()) + { + const int lMaskFlags = + const_cast(this) + ->GetMaskFlags(); + if ((lMaskFlags != GMF_ALL_VALID && + lMaskFlags != GMF_NODATA) || + IsMaskBand()) + { + // Unfortunately this will prevent using overviews + // of the sources, but it is unpractical to use + // them without serious implementation complications + return false; + } + } + } + double dfXOff = nXOff; double dfYOff = nYOff; double dfXSize = nXSize; @@ -388,9 +428,41 @@ CPLErr VRTSourcedRasterBand::IRasterIO( // recursion l_poDS->SetEnableOverviews(false); } + + const auto eResampleAlgBackup = psExtraArg->eResampleAlg; + if (psExtraArg->eResampleAlg == GRIORA_NearestNeighbour) + { + std::string osResampling; + for (int i = 0; i < nSources; i++) + { + if (papoSources[i]->GetType() == + VRTComplexSource::GetTypeStatic()) + { + auto *const poComplexSource = + static_cast(papoSources[i]); + if (!poComplexSource->GetResampling().empty()) + { + if (i == 0) + osResampling = poComplexSource->GetResampling(); + else if (osResampling != + poComplexSource->GetResampling()) + { + osResampling.clear(); + break; + } + } + } + } + if (!osResampling.empty()) + psExtraArg->eResampleAlg = + GDALRasterIOGetResampleAlg(osResampling.c_str()); + } + const auto eErr = GDALRasterBand::IRasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg); + + psExtraArg->eResampleAlg = eResampleAlgBackup; l_poDS->SetEnableOverviews(bBackupEnabledOverviews); return eErr; } From d7d1f8b4ddab6da04150eb2e6e5db6e6de4a655a Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 7 Jan 2025 17:01:48 +0100 Subject: [PATCH 02/14] GRIB: apply a heuristics to auto-fix wrong registration of latitudeOfFirstGridPoint in products from Tokyo center --- ...30103_fake_wrong_grid_origin_latitude.grb2 | Bin 0 -> 193 bytes autotest/gdrivers/grib.py | 15 ++++ frmts/grib/gribdataset.cpp | 77 ++++++++++++++++-- port/cpl_known_config_options.h | 1 + 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 autotest/gdrivers/data/grib/MANAL_2023030103_fake_wrong_grid_origin_latitude.grb2 diff --git a/autotest/gdrivers/data/grib/MANAL_2023030103_fake_wrong_grid_origin_latitude.grb2 b/autotest/gdrivers/data/grib/MANAL_2023030103_fake_wrong_grid_origin_latitude.grb2 new file mode 100644 index 0000000000000000000000000000000000000000..feb969d7bd6eb91e1f4f4bfb2eb0fd1945b52f5e GIT binary patch literal 193 zcmZ<{@^oTgU|<4b5I6|LqKphmU=jA`%#6%nVFm_PkcvPck1bLV$dY4ZQvDhK;6EGy zHCzN@N2Z1GYHaC8g&7!69&X@>ux4QJ@pb@X2js807ymFc^W@ e4Cx?(3!(gds.lon1; // Latitude in degrees, to be transformed to meters. - rMaxY = meta->gds.lat1; + double dfGridOriY = meta->gds.lat1; if (m_poSRS == nullptr || m_poLL == nullptr || !m_poSRS->IsSame(&oSRS) || !m_poLL->IsSame(&oLL)) @@ -2666,13 +2666,78 @@ void GRIBDataset::SetGribMetaData(grib_MetaData *meta) } // Transform it to meters. - if ((m_poCT != nullptr) && m_poCT->Transform(1, &rMinX, &rMaxY)) + if ((m_poCT != nullptr) && m_poCT->Transform(1, &rMinX, &dfGridOriY)) { if (meta->gds.scan == GRIB2BIT_2) // Y is minY, GDAL wants maxY. { - // -1 because we GDAL needs the coordinates of the centre of - // the pixel. - rMaxY += (meta->gds.Ny - 1) * meta->gds.Dy; + const char *pszConfigOpt = CPLGetConfigOption( + "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_MOST", + nullptr); + bool bLatOfFirstPointIsSouthernMost = + !pszConfigOpt || CPLTestBool(pszConfigOpt); + + // Hack for a file called MANAL_2023030103.grb2 that + // uses LCC and has Latitude of false origin = 30 + // Longitude of false origin = 140 + // Latitude of 1st standard parallel = 60 + // Latitude of 2nd standard parallel = 30 + // but whose (meta->gds.lon1, meta->gds.lat1) qualifies the + // northern-most point of the grid and not the bottom-most one + // as it should given the scan == GRIB2BIT_2 + if (!pszConfigOpt && meta->gds.projType == GS3_LAMBERT && + std::fabs(meta->gds.scaleLat1 - 60) <= 1e-8 && + std::fabs(meta->gds.scaleLat2 - 30) <= 1e-8 && + std::fabs(meta->gds.meshLat - 30) <= 1e-8 && + std::fabs(Lon360to180(meta->gds.orientLon) - 140) <= 1e-8) + { + double dfXCenterProj = Lon360to180(meta->gds.orientLon); + double dfYCenterProj = meta->gds.meshLat; + if (m_poCT->Transform(1, &dfXCenterProj, &dfYCenterProj)) + { + double dfXCenterGridNominal = + rMinX + nRasterXSize * meta->gds.Dx / 2; + double dfYCenterGridNominal = + dfGridOriY + nRasterYSize * meta->gds.Dy / 2; + double dfXCenterGridBuggy = dfXCenterGridNominal; + double dfYCenterGridBuggy = + dfGridOriY - nRasterYSize * meta->gds.Dy / 2; + const auto SQR = [](double x) { return x * x; }; + if (SQR(dfXCenterGridBuggy - dfXCenterProj) + + SQR(dfYCenterGridBuggy - dfYCenterProj) < + SQR(10) * + (SQR(dfXCenterGridNominal - dfXCenterProj) + + SQR(dfYCenterGridNominal - dfYCenterProj))) + { + CPLError( + CE_Warning, CPLE_AppDefined, + "Likely buggy grid registration for GRIB2 " + "product: heuristics shows that the " + "latitudeOfFirstGridPoint is likely to qualify " + "the latitude of the northern-most grid point " + "instead of the southern-most grid point as " + "expected. Please report to data producer. " + "This heuristics can be disabled by setting " + "the " + "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_" + "MOST configuration option to YES."); + bLatOfFirstPointIsSouthernMost = false; + } + } + } + if (bLatOfFirstPointIsSouthernMost) + { + // -1 because we GDAL needs the coordinates of the centre of + // the pixel. + rMaxY = dfGridOriY + (meta->gds.Ny - 1) * meta->gds.Dy; + } + else + { + rMaxY = dfGridOriY; + } + } + else + { + rMaxY = dfGridOriY; } rPixelSizeX = meta->gds.Dx; rPixelSizeY = meta->gds.Dy; @@ -2680,7 +2745,7 @@ void GRIBDataset::SetGribMetaData(grib_MetaData *meta) else { rMinX = 0.0; - rMaxY = 0.0; + // rMaxY = 0.0; rPixelSizeX = 1.0; rPixelSizeY = -1.0; diff --git a/port/cpl_known_config_options.h b/port/cpl_known_config_options.h index 942f4612ce1b..00166efe0db8 100644 --- a/port/cpl_known_config_options.h +++ b/port/cpl_known_config_options.h @@ -475,6 +475,7 @@ constexpr static const char* const apszKnownConfigOptions[] = "GRIB_CACHEMAX", // from gribdataset.cpp "GRIB_DEFAULT_SEMI_MAJOR", // from gribdataset.cpp "GRIB_DEFAULT_SEMI_MINOR", // from gribdataset.cpp + "GRIB_LATITUDE_OF_FIRST_GRID_POINT_IS_SOUTHERN_MOST", // from gribdataset.cpp "GRIB_NORMALIZE_UNITS", // from gribdataset.cpp "GRIB_PDS_ALL_BANDS", // from gribdataset.cpp "GRIB_RESOURCE_DIR", // from metaname.cpp From fadf31f5ddfca6bcac2569ee867bcf720ffa6bbf Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Jan 2025 02:24:37 +0100 Subject: [PATCH 03/14] Add a OGRGeometryFactory::GetDefaultArcStepSize() method to get value of OGR_ARC_STEPSIZE config option --- autotest/cpp/test_ogr.cpp | 27 +++++++++++++ ogr/gml2ogrgeometry.cpp | 7 +--- ogr/ogr_geometry.h | 2 + ogr/ogrgeometryfactory.cpp | 46 ++++++++++++++++++++--- ogr/ogrpgeogeometry.cpp | 4 +- ogr/ogrsf_frmts/dwg/ogrdgnv8layer.cpp | 2 +- ogr/ogrsf_frmts/ili/ogrili1datasource.cpp | 11 +----- ogr/ogrsf_frmts/ili/ogrili1layer.cpp | 12 ++---- port/cpl_known_config_options.h | 2 +- 9 files changed, 79 insertions(+), 34 deletions(-) diff --git a/autotest/cpp/test_ogr.cpp b/autotest/cpp/test_ogr.cpp index e748568ad8c0..e6605d9f03b8 100644 --- a/autotest/cpp/test_ogr.cpp +++ b/autotest/cpp/test_ogr.cpp @@ -4571,4 +4571,31 @@ TEST_F(test_ogr, OGRFieldDefnGetFieldTypeByName) } } +// Test OGRGeometryFactory::GetDefaultArcStepSize() +TEST_F(test_ogr, GetDefaultArcStepSize) +{ + if (CPLGetConfigOption("OGR_ARC_STEPSIZE", nullptr) == nullptr) + { + EXPECT_EQ(OGRGeometryFactory::GetDefaultArcStepSize(), 4.0); + } + { + CPLConfigOptionSetter oSetter("OGR_ARC_STEPSIZE", "0.00001", + /* bSetOnlyIfUndefined = */ false); + CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler); + EXPECT_EQ(OGRGeometryFactory::GetDefaultArcStepSize(), 1e-2); + EXPECT_TRUE( + strstr(CPLGetLastErrorMsg(), + "Too small value for OGR_ARC_STEPSIZE. Clamping it to")); + } + { + CPLConfigOptionSetter oSetter("OGR_ARC_STEPSIZE", "190", + /* bSetOnlyIfUndefined = */ false); + CPLErrorHandlerPusher oErrorHandler(CPLQuietErrorHandler); + EXPECT_EQ(OGRGeometryFactory::GetDefaultArcStepSize(), 180); + EXPECT_TRUE( + strstr(CPLGetLastErrorMsg(), + "Too large value for OGR_ARC_STEPSIZE. Clamping it to")); + } +} + } // namespace diff --git a/ogr/gml2ogrgeometry.cpp b/ogr/gml2ogrgeometry.cpp index bd4a59adf386..1ad0ba5c44b1 100644 --- a/ogr/gml2ogrgeometry.cpp +++ b/ogr/gml2ogrgeometry.cpp @@ -1876,9 +1876,7 @@ GML2OGRGeometry_XMLNode_Internal(const CPLXMLNode *psNode, const char *pszId, if (bSRSUnitIsDegree && dfUOMConv > 0) { auto poLS = std::make_unique(); - // coverity[tainted_data] - const double dfStep = - CPLAtof(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4")); + const double dfStep = OGRGeometryFactory::GetDefaultArcStepSize(); const double dfSign = dfStartAngle < dfEndAngle ? 1 : -1; for (double dfAngle = dfStartAngle; (dfAngle - dfEndAngle) * dfSign < 0; @@ -2010,8 +2008,7 @@ GML2OGRGeometry_XMLNode_Internal(const CPLXMLNode *psNode, const char *pszId, if (bSRSUnitIsDegree && dfUOMConv > 0) { auto poLS = std::make_unique(); - const double dfStep = - CPLAtof(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4")); + const double dfStep = OGRGeometryFactory::GetDefaultArcStepSize(); for (double dfAngle = 0; dfAngle < 360; dfAngle += dfStep) { double dfLong = 0.0; diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 414d5d2829b4..42dd9a69d0e1 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -4381,6 +4381,8 @@ class CPL_DLL OGRGeometryFactory char **papszOptions, const TransformWithOptionsCache &cache = TransformWithOptionsCache()); + static double GetDefaultArcStepSize(); + static OGRGeometry * approximateArcAngles(double dfX, double dfY, double dfZ, double dfPrimaryRadius, double dfSecondaryAxis, diff --git a/ogr/ogrgeometryfactory.cpp b/ogr/ogrgeometryfactory.cpp index 86d6c6b4b3db..7d09f1b7b180 100644 --- a/ogr/ogrgeometryfactory.cpp +++ b/ogr/ogrgeometryfactory.cpp @@ -4197,13 +4197,47 @@ void OGR_GeomTransformer_Destroy(OGRGeomTransformerH hTransformer) } /************************************************************************/ -/* OGRGF_GetDefaultStepSize() */ +/* OGRGeometryFactory::GetDefaultArcStepSize() */ /************************************************************************/ -static double OGRGF_GetDefaultStepSize() +/** Return the default value of the angular step used when stroking curves + * as lines. Defaults to 4 degrees. + * Can be modified by setting the OGR_ARC_STEPSIZE configuration option. + * Valid values are in [1e-2, 180] degree range. + * @since 3.11 + */ + +/* static */ +double OGRGeometryFactory::GetDefaultArcStepSize() { - // coverity[tainted_data] - return CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4")); + const double dfVal = CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4")); + constexpr double MIN_VAL = 1e-2; + if (dfVal < MIN_VAL) + { + static bool bWarned = false; + if (!bWarned) + { + bWarned = true; + CPLError(CE_Warning, CPLE_AppDefined, + "Too small value for OGR_ARC_STEPSIZE. Clamping it to %f", + MIN_VAL); + } + return MIN_VAL; + } + constexpr double MAX_VAL = 180; + if (dfVal > MAX_VAL) + { + static bool bWarned = false; + if (!bWarned) + { + bWarned = true; + CPLError(CE_Warning, CPLE_AppDefined, + "Too large value for OGR_ARC_STEPSIZE. Clamping it to %f", + MAX_VAL); + } + return MAX_VAL; + } + return dfVal; } /************************************************************************/ @@ -4266,7 +4300,7 @@ OGRGeometry *OGRGeometryFactory::approximateArcAngles( // Support default arc step setting. if (dfMaxAngleStepSizeDegrees < 1e-6) { - dfMaxAngleStepSizeDegrees = OGRGF_GetDefaultStepSize(); + dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize(); } // Determine maximum interpolation gap. This is the largest straight-line @@ -5459,7 +5493,7 @@ OGRLineString *OGRGeometryFactory::curveToLineString( // support default arc step setting. if (dfMaxAngleStepSizeDegrees < 1e-6) { - dfMaxAngleStepSizeDegrees = OGRGF_GetDefaultStepSize(); + dfMaxAngleStepSizeDegrees = OGRGeometryFactory::GetDefaultArcStepSize(); } double dfStep = dfMaxAngleStepSizeDegrees / 180 * M_PI; diff --git a/ogr/ogrpgeogeometry.cpp b/ogr/ogrpgeogeometry.cpp index af0a1eff784c..86005a315b3c 100644 --- a/ogr/ogrpgeogeometry.cpp +++ b/ogr/ogrpgeogeometry.cpp @@ -1835,10 +1835,8 @@ static OGRCurve *OGRShapeCreateCompoundCurve(int nPartStartIdx, int nPartPoints, dfStartAngle += 2 * M_PI; else if (dfEndAngle + M_PI < dfStartAngle) dfEndAngle += 2 * M_PI; - // coverity[tainted_data] const double dfStepSizeRad = - CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4")) / 180.0 * - M_PI; + OGRGeometryFactory::GetDefaultArcStepSize() / 180.0 * M_PI; const double dfLengthTangentStart = (dfX1 - dfX0) * (dfX1 - dfX0) + (dfY1 - dfY0) * (dfY1 - dfY0); const double dfLengthTangentEnd = diff --git a/ogr/ogrsf_frmts/dwg/ogrdgnv8layer.cpp b/ogr/ogrsf_frmts/dwg/ogrdgnv8layer.cpp index 00b8dd9835f4..48d23edd9a39 100644 --- a/ogr/ogrsf_frmts/dwg/ogrdgnv8layer.cpp +++ b/ogr/ogrsf_frmts/dwg/ogrdgnv8layer.cpp @@ -574,7 +574,7 @@ static void ProcessCurve(OGRFeature *poFeature, const CPLString &osPen, else poSC = new OGRLineString(); const double dfArcStepSize = - CPLAtofM(CPLGetConfigOption("OGR_ARC_STEPSIZE", "4")); + OGRGeometryFactory::GetDefaultArcStepSize(); if (!ellipse.isNull()) { nPoints = std::max(2, static_cast(360 / dfArcStepSize)); diff --git a/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp b/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp index bc9f8ba3dd47..6dd848af7bdd 100644 --- a/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp +++ b/ogr/ogrsf_frmts/ili/ogrili1datasource.cpp @@ -148,19 +148,12 @@ int OGRILI1DataSource::Open(const char *pszNewName, char **papszOpenOptionsIn, if (osModelFilename.length() > 0) poReader->ReadModel(poImdReader, osModelFilename.c_str(), this); - int bResetConfigOption = FALSE; - if (EQUAL(CPLGetConfigOption("OGR_ARC_STEPSIZE", ""), "")) - { - bResetConfigOption = TRUE; - CPLSetThreadLocalConfigOption("OGR_ARC_STEPSIZE", "0.96"); - } + CPLConfigOptionSetter oSetter("OGR_ARC_STEPSIZE", "0.96", + /* bSetOnlyIfUndefined = */ true); // Parse model and read data - without surface join and area polygonizing. poReader->ReadFeatures(); - if (bResetConfigOption) - CPLSetThreadLocalConfigOption("OGR_ARC_STEPSIZE", nullptr); - return TRUE; } diff --git a/ogr/ogrsf_frmts/ili/ogrili1layer.cpp b/ogr/ogrsf_frmts/ili/ogrili1layer.cpp index 2d4b615e035f..b188c17dceaa 100644 --- a/ogr/ogrsf_frmts/ili/ogrili1layer.cpp +++ b/ogr/ogrsf_frmts/ili/ogrili1layer.cpp @@ -444,12 +444,9 @@ OGRErr OGRILI1Layer::CreateField(const OGRFieldDefn *poField, void OGRILI1Layer::JoinGeomLayers() { bGeomsJoined = true; - bool bResetConfigOption = false; - if (EQUAL(CPLGetConfigOption("OGR_ARC_STEPSIZE", ""), "")) - { - bResetConfigOption = true; - CPLSetThreadLocalConfigOption("OGR_ARC_STEPSIZE", "0.96"); - } + + CPLConfigOptionSetter oSetter("OGR_ARC_STEPSIZE", "0.96", + /* bSetOnlyIfUndefined = */ true); for (GeomFieldInfos::const_iterator it = oGeomFieldInfos.begin(); it != oGeomFieldInfos.end(); ++it) @@ -477,9 +474,6 @@ void OGRILI1Layer::JoinGeomLayers() } } } - - if (bResetConfigOption) - CPLSetThreadLocalConfigOption("OGR_ARC_STEPSIZE", nullptr); } void OGRILI1Layer::JoinSurfaceLayer(OGRILI1Layer *poSurfaceLineLayer, diff --git a/port/cpl_known_config_options.h b/port/cpl_known_config_options.h index 942f4612ce1b..cbac9be2bbde 100644 --- a/port/cpl_known_config_options.h +++ b/port/cpl_known_config_options.h @@ -663,7 +663,7 @@ constexpr static const char* const apszKnownConfigOptions[] = "OGR_API_SPY_SNAPSHOT_PATH", // from ograpispy.cpp "OGR_APPLY_GEOM_SET_PRECISION", // from ogr2ogr_lib.cpp, ogrlayer.cpp "OGR_ARC_MAX_GAP", // from ogrgeometryfactory.cpp - "OGR_ARC_STEPSIZE", // from gml2ogrgeometry.cpp, ogrdgnv8layer.cpp, ogrgeometryfactory.cpp, ogrili1datasource.cpp, ogrili1layer.cpp, ogrpgeogeometry.cpp + "OGR_ARC_STEPSIZE", // from ogrgeometryfactory.cpp "OGR_ARROW_COMPUTE_GEOMETRY_TYPE", // from ogrfeatherlayer.cpp "OGR_ARROW_LOAD_FILE_SYSTEM_FACTORIES", // from ogrfeatherdriver.cpp "OGR_ARROW_MEM_LIMIT", // from ograrrowarrayhelper.cpp From ad15a8e141c4841ef1696ad88d0ea55272c82d01 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Jan 2025 02:47:31 +0100 Subject: [PATCH 04/14] Add CPLErrorOnce() and CPLDebugOnce() to emit an error/debug message only once during the life-time of a process. --- alg/gdalwarpkernel.cpp | 7 +-- apps/ogr2ogr_lib.cpp | 5 +-- frmts/aigrid/gridlib.c | 10 ++--- frmts/bsb/bsb_read.c | 7 +-- frmts/gtiff/geotiff.cpp | 7 +-- frmts/hfa/hfaband.cpp | 7 +-- frmts/vrt/vrtsources.cpp | 7 +-- gcore/gdalrasterblock.cpp | 14 +----- ogr/ogrct.cpp | 17 ++----- ogr/ogrgeojsonwriter.cpp | 7 +-- ogr/ogrgeometryfactory.cpp | 27 +++-------- ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp | 15 +++---- ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp | 7 +-- ogr/ogrsf_frmts/gmlas/ogrgmlasxsdcache.cpp | 14 +----- .../parquet/ogrparquetwriterlayer.cpp | 17 +++---- ogr/ogrspatialreference.cpp | 14 +----- port/cpl_error.h | 45 +++++++++++++++++++ 17 files changed, 83 insertions(+), 144 deletions(-) diff --git a/alg/gdalwarpkernel.cpp b/alg/gdalwarpkernel.cpp index b9a18dcb4102..37aedda5ff4d 100644 --- a/alg/gdalwarpkernel.cpp +++ b/alg/gdalwarpkernel.cpp @@ -1240,13 +1240,8 @@ CPLErr GDALWarpKernel::PerformWarp() if (pafUnifiedSrcDensity != nullptr) { // Typically if there's a cutline or an alpha band - static bool bHasWarned = false; - if (!bHasWarned) - { - bHasWarned = true; - CPLDebug("WARP", "pafUnifiedSrcDensity is not null, " + CPLDebugOnce("WARP", "pafUnifiedSrcDensity is not null, " "hence OpenCL warper cannot be used"); - } } else { diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index 8ae94739db75..ea3e52bba62e 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -5987,12 +5987,9 @@ SetupCT(TargetLayerInfo *psInfo, OGRLayer *poSrcLayer, bool bTransform, } else { - static bool bHasWarned = false; - if (!bHasWarned) - CPLError(CE_Failure, CPLE_IllegalArg, + CPLErrorOnce(CE_Failure, CPLE_IllegalArg, "-wrapdateline option only works when " "reprojecting to a geographic SRS"); - bHasWarned = true; } psInfo->m_aoReprojectionInfo[iGeom].m_aosTransformOptions.Assign( diff --git a/frmts/aigrid/gridlib.c b/frmts/aigrid/gridlib.c index fb1ae8a1be4f..ac4423c82536 100644 --- a/frmts/aigrid/gridlib.c +++ b/frmts/aigrid/gridlib.c @@ -13,6 +13,8 @@ #include "aigrid.h" +#include + #ifndef CPL_IGNORE_RET_VAL_INT_defined #define CPL_IGNORE_RET_VAL_INT_defined @@ -766,21 +768,15 @@ CPLErr AIGReadBlock(VSILFILE *fp, GUInt32 nBlockOffset, int nBlockSize, if (eErr == CE_Failure) { - static int bHasWarned = FALSE; - for (i = 0; i < nBlockXSize * nBlockYSize; i++) panData[i] = ESRI_GRID_NO_DATA; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "Unsupported Arc/Info Binary Grid tile of type 0x%X" " encountered.\n" "This and subsequent unsupported tile types set to" " no data value.\n", nMagic); - bHasWarned = TRUE; - } } } diff --git a/frmts/bsb/bsb_read.c b/frmts/bsb/bsb_read.c index ede8061a0ab7..3992968b0967 100644 --- a/frmts/bsb/bsb_read.c +++ b/frmts/bsb/bsb_read.c @@ -849,12 +849,7 @@ int BSBReadScanline(BSBInfo *psInfo, int nScanline, } if (nRunCount > psInfo->nXSize) { - static int bHasWarned = FALSE; - if (!bHasWarned) - { - CPLDebug("BSB", "Too big run count : %d", nRunCount); - bHasWarned = TRUE; - } + CPLDebugOnce("BSB", "Too big run count : %d", nRunCount); } if (iPixel + nRunCount + 1 > psInfo->nXSize) diff --git a/frmts/gtiff/geotiff.cpp b/frmts/gtiff/geotiff.cpp index db72d8d9faf4..bd27e8bfe681 100644 --- a/frmts/gtiff/geotiff.cpp +++ b/frmts/gtiff/geotiff.cpp @@ -93,16 +93,11 @@ void GTIFFGetOverviewBlockSize(GDALRasterBandH hBand, int *pnBlockXSize, if (nOvrBlockSize < 64 || nOvrBlockSize > 4096 || !CPLIsPowerOfTwo(nOvrBlockSize)) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_NotSupported, + CPLErrorOnce(CE_Warning, CPLE_NotSupported, "Wrong value for GDAL_TIFF_OVR_BLOCKSIZE : %s. " "Should be a power of 2 between 64 and 4096. " "Defaulting to 128", pszVal); - bHasWarned = true; - } nOvrBlockSize = 128; } diff --git a/frmts/hfa/hfaband.cpp b/frmts/hfa/hfaband.cpp index 76115be0fa90..c4d17573c80e 100644 --- a/frmts/hfa/hfaband.cpp +++ b/frmts/hfa/hfaband.cpp @@ -2028,16 +2028,11 @@ static int HFAGetOverviewBlockSize() if (nOvrBlockSize < 32 || nOvrBlockSize > 2048 || !CPLIsPowerOfTwo(nOvrBlockSize)) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_NotSupported, + CPLErrorOnce(CE_Warning, CPLE_NotSupported, "Wrong value for GDAL_HFA_OVR_BLOCKSIZE : %s. " "Should be a power of 2 between 32 and 2048. " "Defaulting to 64", pszVal); - bHasWarned = true; - } nOvrBlockSize = 64; } diff --git a/frmts/vrt/vrtsources.cpp b/frmts/vrt/vrtsources.cpp index 0fdd498d368e..c6dbbc2ec4e6 100644 --- a/frmts/vrt/vrtsources.cpp +++ b/frmts/vrt/vrtsources.cpp @@ -3535,13 +3535,8 @@ CPLErr VRTComplexSource::RasterIOInternal( } else { - static bool bHasWarned = false; - if (!bHasWarned) - { - bHasWarned = true; - CPLError(CE_Failure, CPLE_AppDefined, + CPLErrorOnce(CE_Failure, CPLE_AppDefined, "No entry %d.", static_cast(fResult)); - } continue; } } diff --git a/gcore/gdalrasterblock.cpp b/gcore/gdalrasterblock.cpp index b483b9e6e381..f8e27a340993 100644 --- a/gcore/gdalrasterblock.cpp +++ b/gcore/gdalrasterblock.cpp @@ -184,14 +184,9 @@ int CPL_STDCALL GDALGetCacheMax() GIntBig nRes = GDALGetCacheMax64(); if (nRes > INT_MAX) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "Cache max value doesn't fit on a 32 bit integer. " "Call GDALGetCacheMax64() instead"); - bHasWarned = true; - } nRes = INT_MAX; } return static_cast(nRes); @@ -280,14 +275,9 @@ int CPL_STDCALL GDALGetCacheUsed() { if (nCacheUsed > INT_MAX) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "Cache used value doesn't fit on a 32 bit integer. " "Call GDALGetCacheUsed64() instead"); - bHasWarned = true; - } return INT_MAX; } return static_cast(nCacheUsed); diff --git a/ogr/ogrct.cpp b/ogr/ogrct.cpp index 621a2f69f607..4da6f38a037f 100644 --- a/ogr/ogrct.cpp +++ b/ogr/ogrct.cpp @@ -1764,15 +1764,10 @@ static PJ *op_to_pj(PJ_CONTEXT *ctx, PJ *op, const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr); if (pszUseETMERC && pszUseETMERC[0]) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "OSR_USE_ETMERC is a legacy configuration option, which " "now has only effect when set to NO (YES is the default). " "Use OSR_USE_APPROX_TMERC=YES instead"); - bHasWarned = true; - } bForceApproxTMerc = !CPLTestBool(pszUseETMERC); } else @@ -2737,18 +2732,14 @@ int OGRProjCT::TransformWithErrorCodes(size_t nCount, double *x, double *y, x[i] = HUGE_VAL; y[i] = HUGE_VAL; err = PROJ_ERR_COORD_TRANSFM_OUTSIDE_PROJECTION_DOMAIN; - static bool bHasWarned = false; - if (!bHasWarned) - { + #ifdef DEBUG - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "PROJ returned a NaN value. It should be fixed"); #else - CPLDebug("OGR_CT", + CPLDebugOnce("OGR_CT", "PROJ returned a NaN value. It should be fixed"); #endif - bHasWarned = true; - } } else if (coord.xyzt.x == HUGE_VAL) { diff --git a/ogr/ogrgeojsonwriter.cpp b/ogr/ogrgeojsonwriter.cpp index b234fbe7ca87..d22f58e6eacd 100644 --- a/ogr/ogrgeojsonwriter.cpp +++ b/ogr/ogrgeojsonwriter.cpp @@ -968,13 +968,8 @@ json_object *OGRGeoJSONWriteAttributes(OGRFeature *poFeature, { if (!oOptions.bAllowNonFiniteValues) { - static bool bHasWarned = false; - if (!bHasWarned) - { - bHasWarned = true; - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "NaN of Infinity value found. Skipped"); - } continue; } } diff --git a/ogr/ogrgeometryfactory.cpp b/ogr/ogrgeometryfactory.cpp index 7d09f1b7b180..8748ccbef7b7 100644 --- a/ogr/ogrgeometryfactory.cpp +++ b/ogr/ogrgeometryfactory.cpp @@ -4024,15 +4024,10 @@ OGRGeometry *OGRGeometryFactory::transformWithOptions( if (poDstGeom->getSpatialReference() && !poDstGeom->getSpatialReference()->IsGeographic()) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError( - CE_Warning, CPLE_AppDefined, - "WRAPDATELINE is without effect when reprojecting to a " - "non-geographic CRS"); - bHasWarned = true; - } + CPLErrorOnce( + CE_Warning, CPLE_AppDefined, + "WRAPDATELINE is without effect when reprojecting to a " + "non-geographic CRS"); return poDstGeom.release(); } // TODO and we should probably also test that the axis order + data axis @@ -4214,27 +4209,17 @@ double OGRGeometryFactory::GetDefaultArcStepSize() constexpr double MIN_VAL = 1e-2; if (dfVal < MIN_VAL) { - static bool bWarned = false; - if (!bWarned) - { - bWarned = true; - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "Too small value for OGR_ARC_STEPSIZE. Clamping it to %f", MIN_VAL); - } return MIN_VAL; } constexpr double MAX_VAL = 180; if (dfVal > MAX_VAL) { - static bool bWarned = false; - if (!bWarned) - { - bWarned = true; - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "Too large value for OGR_ARC_STEPSIZE. Clamping it to %f", MAX_VAL); - } return MAX_VAL; } return dfVal; diff --git a/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp b/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp index 22dd2cddc426..4582d7d9d828 100644 --- a/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp +++ b/ogr/ogrsf_frmts/elastic/ogrelasticlayer.cpp @@ -2405,16 +2405,11 @@ CPLString OGRElasticLayer::BuildJSonFromFeature(OGRFeature *poFeature) else if (env.MinX < -180 || env.MinY < -90 || env.MaxX > 180 || env.MaxY > 90) { - static bool bHasWarned = false; - if (!bHasWarned) - { - bHasWarned = true; - CPLError( - CE_Warning, CPLE_AppDefined, - "At least one geometry has a bounding box outside " - "of [-180,180] longitude range and/or [-90,90] " - "latitude range. Undefined behavior"); - } + CPLErrorOnce( + CE_Warning, CPLE_AppDefined, + "At least one geometry has a bounding box outside " + "of [-180,180] longitude range and/or [-90,90] " + "latitude range. Undefined behavior"); } std::vector aosPath = m_aaosGeomFieldPaths[i]; diff --git a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp index 79390d0b3f0f..1b50500a030f 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbLayer.cpp @@ -1302,16 +1302,11 @@ OGRErr FGdbLayer::PopulateRowWithFeature(Row &fgdb_row, OGRFeature *poFeature) { if (fldvalue < -32768 || fldvalue > 32767) { - static int bHasWarned = FALSE; - if (!bHasWarned) - { - bHasWarned = TRUE; - CPLError(CE_Warning, CPLE_NotSupported, + CPLErrorOnce(CE_Warning, CPLE_NotSupported, "Value %d for field %s does not fit into a " "short and will be clamped. " "This warning will not be emitted any more", fldvalue, field_name.c_str()); - } if (fldvalue < -32768) fldvalue = -32768; else diff --git a/ogr/ogrsf_frmts/gmlas/ogrgmlasxsdcache.cpp b/ogr/ogrsf_frmts/gmlas/ogrgmlasxsdcache.cpp index bc4f3e0473dc..bf9abaf108de 100644 --- a/ogr/ogrsf_frmts/gmlas/ogrgmlasxsdcache.cpp +++ b/ogr/ogrsf_frmts/gmlas/ogrgmlasxsdcache.cpp @@ -176,12 +176,7 @@ bool GMLASXSDCache::CacheAllGML321() CPLHTTPDestroyResult(psResult); if (!bSuccess) { - static bool bHasWarned = false; - if (!bHasWarned) - { - bHasWarned = true; - CPLDebug("GMLAS", "Cannot get GML schemas from %s", pszHTTPZIP); - } + CPLDebugOnce("GMLAS", "Cannot get GML schemas from %s", pszHTTPZIP); } return bSuccess; } @@ -248,12 +243,7 @@ bool GMLASXSDCache::CacheAllISO20070417() CPLHTTPDestroyResult(psResult); if (!bSuccess) { - static bool bHasWarned = false; - if (!bHasWarned) - { - bHasWarned = true; - CPLDebug("GMLAS", "Cannot get ISO schemas from %s", pszHTTPZIP); - } + CPLDebugOnce("GMLAS", "Cannot get ISO schemas from %s", pszHTTPZIP); } return bSuccess; } diff --git a/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp b/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp index d647d355c88a..d912c0b7b23c 100644 --- a/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp +++ b/ogr/ogrsf_frmts/parquet/ogrparquetwriterlayer.cpp @@ -356,17 +356,12 @@ bool OGRParquetWriterLayer::SetOptions(CSLConstList papszOptions, m_eGeomEncoding = OGRArrowGeomEncoding::WKT; else if (EQUAL(pszGeomEncoding, "GEOARROW_INTERLEAVED")) { - static bool bHasWarned = false; - if (!bHasWarned) - { - bHasWarned = true; - CPLError( - CE_Warning, CPLE_AppDefined, - "Use of GEOMETRY_ENCODING=GEOARROW_INTERLEAVED is not " - "recommended. " - "GeoParquet 1.1 uses GEOMETRY_ENCODING=GEOARROW (struct) " - "instead."); - } + CPLErrorOnce( + CE_Warning, CPLE_AppDefined, + "Use of GEOMETRY_ENCODING=GEOARROW_INTERLEAVED is not " + "recommended. " + "GeoParquet 1.1 uses GEOMETRY_ENCODING=GEOARROW (struct) " + "instead."); m_eGeomEncoding = OGRArrowGeomEncoding::GEOARROW_FSL_GENERIC; } else if (EQUAL(pszGeomEncoding, "GEOARROW") || diff --git a/ogr/ogrspatialreference.cpp b/ogr/ogrspatialreference.cpp index feaf79babdf3..5d4477a40573 100644 --- a/ogr/ogrspatialreference.cpp +++ b/ogr/ogrspatialreference.cpp @@ -11469,14 +11469,9 @@ OGRErr OGRSpatialReference::importFromProj4(const char *pszProj4) if (osProj4.find("+init=epsg:") != std::string::npos && getenv("PROJ_USE_PROJ4_INIT_RULES") == nullptr) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "+init=epsg:XXXX syntax is deprecated. It might return " "a CRS with a non-EPSG compliant axis order."); - bHasWarned = true; - } } proj_context_use_proj4_init_rules(d->getPROJContext(), true); d->setPjCRS(proj_create(d->getPROJContext(), osProj4.c_str())); @@ -11571,15 +11566,10 @@ OGRErr OGRSpatialReference::exportToProj4(char **ppszProj4) const const char *pszUseETMERC = CPLGetConfigOption("OSR_USE_ETMERC", nullptr); if (pszUseETMERC && pszUseETMERC[0]) { - static bool bHasWarned = false; - if (!bHasWarned) - { - CPLError(CE_Warning, CPLE_AppDefined, + CPLErrorOnce(CE_Warning, CPLE_AppDefined, "OSR_USE_ETMERC is a legacy configuration option, which " "now has only effect when set to NO (YES is the default). " "Use OSR_USE_APPROX_TMERC=YES instead"); - bHasWarned = true; - } bForceApproxTMerc = !CPLTestBool(pszUseETMERC); } else diff --git a/port/cpl_error.h b/port/cpl_error.h index fc608dfb0b75..748d61555c5c 100644 --- a/port/cpl_error.h +++ b/port/cpl_error.h @@ -125,6 +125,23 @@ typedef int CPLErrorNum; void CPL_DLL CPLError(CPLErr eErrClass, CPLErrorNum err_no, CPL_FORMAT_STRING(const char *fmt), ...) CPL_PRINT_FUNC_FORMAT(3, 4); + +/** Similar to CPLError(), but only execute it once during the life-time + * of a process. + * + * @since 3.11 + */ +#define CPLErrorOnce(...) \ + do \ + { \ + static bool lbCPLErrorOnce = false; \ + if (!lbCPLErrorOnce) \ + { \ + lbCPLErrorOnce = true; \ + CPLError(__VA_ARGS__); \ + } \ + } while (0) + void CPL_DLL CPLErrorV(CPLErr, CPLErrorNum, const char *, va_list); void CPL_DLL CPLEmergencyError(const char *) CPL_NO_RETURN; void CPL_DLL CPL_STDCALL CPLErrorReset(void); @@ -171,11 +188,39 @@ void CPL_DLL CPL_STDCALL CPLPopErrorHandler(void); do \ { \ } while (0) /* Eat all CPLDebugProgress calls. */ + +/** Similar to CPLDebug(), but only execute it once during the life-time + * of a process. + * + * @since 3.11 + */ +#define CPLDebugOnce(...) \ + do \ + { \ + } while (0) + #else void CPL_DLL CPLDebug(const char *, CPL_FORMAT_STRING(const char *), ...) CPL_PRINT_FUNC_FORMAT(2, 3); void CPL_DLL CPLDebugProgress(const char *, CPL_FORMAT_STRING(const char *), ...) CPL_PRINT_FUNC_FORMAT(2, 3); + +/** Similar to CPLDebug(), but only execute it once during the life-time + * of a process. + * + * @since 3.11 + */ +#define CPLDebugOnce(...) \ + do \ + { \ + static bool lbCPLErrorOnce = false; \ + if (!lbCPLErrorOnce) \ + { \ + lbCPLErrorOnce = true; \ + CPLDebug(__VA_ARGS__); \ + } \ + } while (0) + #endif #if defined(DEBUG) || defined(GDAL_DEBUG) From d80db05f60e757ad7f37f28c4430d608e5a18e85 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Jan 2025 17:42:53 +0100 Subject: [PATCH 05/14] Add GDALExpandPackedBitsToByteAt0Or255() and GDALExpandPackedBitsToByteAt0Or1() --- autotest/cpp/test_gdal.cpp | 68 +++++++++++++ gcore/gdal_priv.h | 8 ++ gcore/rasterio.cpp | 194 +++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+) diff --git a/autotest/cpp/test_gdal.cpp b/autotest/cpp/test_gdal.cpp index 7ae64a189bad..e9fde5c5a900 100644 --- a/autotest/cpp/test_gdal.cpp +++ b/autotest/cpp/test_gdal.cpp @@ -5124,4 +5124,72 @@ TEST_F(test_gdal, GDALTranspose2D_Byte_optims) } } +TEST_F(test_gdal, GDALExpandPackedBitsToByteAt0Or1) +{ + unsigned next = 1; + const auto badRand = [&next]() + { + next = static_cast(static_cast(next) * 1103515245 + + 12345); + return next; + }; + + constexpr int BITS_PER_BYTE = 8; + constexpr int SSE_REGISTER_SIZE_IN_BYTES = 16; + constexpr int LESS_THAN_8BITS = 5; + std::vector expectedOut(SSE_REGISTER_SIZE_IN_BYTES * BITS_PER_BYTE + + BITS_PER_BYTE + LESS_THAN_8BITS); + std::vector in((expectedOut.size() + BITS_PER_BYTE - 1) / + BITS_PER_BYTE); + for (int i = 0; i < static_cast(expectedOut.size()); ++i) + { + expectedOut[i] = (badRand() % 2) == 0 ? 0 : 1; + if (expectedOut[i]) + { + in[i / BITS_PER_BYTE] = static_cast( + in[i / BITS_PER_BYTE] | + (1 << (BITS_PER_BYTE - 1 - (i % BITS_PER_BYTE)))); + } + } + + std::vector out(expectedOut.size()); + GDALExpandPackedBitsToByteAt0Or1(in.data(), out.data(), out.size()); + + EXPECT_EQ(out, expectedOut); +} + +TEST_F(test_gdal, GDALExpandPackedBitsToByteAt0Or255) +{ + unsigned next = 1; + const auto badRand = [&next]() + { + next = static_cast(static_cast(next) * 1103515245 + + 12345); + return next; + }; + + constexpr int BITS_PER_BYTE = 8; + constexpr int SSE_REGISTER_SIZE_IN_BYTES = 16; + constexpr int LESS_THAN_8BITS = 5; + std::vector expectedOut(SSE_REGISTER_SIZE_IN_BYTES * BITS_PER_BYTE + + BITS_PER_BYTE + LESS_THAN_8BITS); + std::vector in((expectedOut.size() + BITS_PER_BYTE - 1) / + BITS_PER_BYTE); + for (int i = 0; i < static_cast(expectedOut.size()); ++i) + { + expectedOut[i] = (badRand() % 2) == 0 ? 0 : 255; + if (expectedOut[i]) + { + in[i / BITS_PER_BYTE] = static_cast( + in[i / BITS_PER_BYTE] | + (1 << (BITS_PER_BYTE - 1 - (i % BITS_PER_BYTE)))); + } + } + + std::vector out(expectedOut.size()); + GDALExpandPackedBitsToByteAt0Or255(in.data(), out.data(), out.size()); + + EXPECT_EQ(out, expectedOut); +} + } // namespace diff --git a/gcore/gdal_priv.h b/gcore/gdal_priv.h index bed0695c1814..5ec00f099baf 100644 --- a/gcore/gdal_priv.h +++ b/gcore/gdal_priv.h @@ -4493,6 +4493,14 @@ int CPL_DLL GDALReadTabFile2(const char *pszBaseFilename, void CPL_DLL GDALCopyRasterIOExtraArg(GDALRasterIOExtraArg *psDestArg, GDALRasterIOExtraArg *psSrcArg); +void CPL_DLL GDALExpandPackedBitsToByteAt0Or1( + const GByte *CPL_RESTRICT pabyInput, GByte *CPL_RESTRICT pabyOutput, + size_t nInputBits); + +void CPL_DLL GDALExpandPackedBitsToByteAt0Or255( + const GByte *CPL_RESTRICT pabyInput, GByte *CPL_RESTRICT pabyOutput, + size_t nInputBits); + CPL_C_END std::unique_ptr CPL_DLL diff --git a/gcore/rasterio.cpp b/gcore/rasterio.cpp index 2f0ef7ba8ebf..65f2a4718d17 100644 --- a/gcore/rasterio.cpp +++ b/gcore/rasterio.cpp @@ -51,6 +51,9 @@ #ifdef HAVE_SSSE3_AT_COMPILE_TIME #include "rasterio_ssse3.h" +#ifdef __SSSE3__ +#include +#endif #endif static void GDALFastCopyByte(const GByte *CPL_RESTRICT pSrcData, @@ -6137,3 +6140,194 @@ void GDALTranspose2D(const void *pSrc, GDALDataType eSrcType, void *pDst, #undef CALL_GDALTranspose2D_internal } + +/************************************************************************/ +/* ExtractBitAndConvertTo255() */ +/************************************************************************/ + +#if defined(__GNUC__) || defined(_MSC_VER) +// Signedness of char implementation dependent, so be explicit. +// Assumes 2-complement integer types and sign extension of right shifting +// GCC guarantees such: +// https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation +static inline GByte ExtractBitAndConvertTo255(GByte byVal, int nBit) +{ + return static_cast(static_cast(byVal << (7 - nBit)) >> + 7); +} +#else +// Portable way +static inline GByte ExtractBitAndConvertTo255(GByte byVal, int nBit) +{ + return (byVal & (1 << nBit)) ? 255 : 0; +} +#endif + +/************************************************************************/ +/* ExpandEightPackedBitsToByteAt255() */ +/************************************************************************/ + +static inline void ExpandEightPackedBitsToByteAt255(GByte byVal, + GByte abyOutput[8]) +{ + abyOutput[0] = ExtractBitAndConvertTo255(byVal, 7); + abyOutput[1] = ExtractBitAndConvertTo255(byVal, 6); + abyOutput[2] = ExtractBitAndConvertTo255(byVal, 5); + abyOutput[3] = ExtractBitAndConvertTo255(byVal, 4); + abyOutput[4] = ExtractBitAndConvertTo255(byVal, 3); + abyOutput[5] = ExtractBitAndConvertTo255(byVal, 2); + abyOutput[6] = ExtractBitAndConvertTo255(byVal, 1); + abyOutput[7] = ExtractBitAndConvertTo255(byVal, 0); +} + +/************************************************************************/ +/* GDALExpandPackedBitsToByteAt0Or255() */ +/************************************************************************/ + +/** Expand packed-bits (ordered from most-significant bit to least one) + into a byte each, where a bit at 0 is expanded to a byte at 0, and a bit + at 1 to a byte at 255. + + The function does (in a possibly more optimized way) the following: + \code{.cpp} + for (size_t i = 0; i < nInputBits; ++i ) + { + pabyOutput[i] = (pabyInput[i / 8] & (1 << (7 - (i % 8)))) ? 255 : 0; + } + \endcode + + @param pabyInput Input array of (nInputBits + 7) / 8 bytes. + @param pabyOutput Output array of nInputBits bytes. + @param nInputBits Number of valid bits in pabyInput. + + @since 3.11 +*/ + +void GDALExpandPackedBitsToByteAt0Or255(const GByte *CPL_RESTRICT pabyInput, + GByte *CPL_RESTRICT pabyOutput, + size_t nInputBits) +{ + const size_t nInputWholeBytes = nInputBits / 8; + size_t iByte = 0; + +#ifdef HAVE_SSE2 + // Mask to isolate each bit + const __m128i bit_mask = _mm_set_epi8(1, 2, 4, 8, 16, 32, 64, -128, 1, 2, 4, + 8, 16, 32, 64, -128); + const __m128i zero = _mm_setzero_si128(); + const __m128i all_ones = _mm_set1_epi8(-1); +#ifdef __SSSE3__ + const __m128i dispatch_two_bytes = + _mm_set_epi8(1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0); +#endif + constexpr size_t SSE_REG_SIZE = sizeof(bit_mask); + for (; iByte + SSE_REG_SIZE <= nInputWholeBytes; iByte += SSE_REG_SIZE) + { + __m128i reg_ori = _mm_loadu_si128( + reinterpret_cast(pabyInput + iByte)); + + constexpr int NUM_PROCESSED_BYTES_PER_REG = 2; + for (size_t k = 0; k < SSE_REG_SIZE / NUM_PROCESSED_BYTES_PER_REG; ++k) + { + // Given reg_ori = (A, B, ... 14 other bytes ...), + // expand to (A, A, A, A, A, A, A, A, B, B, B, B, B, B, B, B) +#ifdef __SSSE3__ + __m128i reg = _mm_shuffle_epi8(reg_ori, dispatch_two_bytes); +#else + __m128i reg = _mm_unpacklo_epi8(reg_ori, reg_ori); + reg = _mm_unpacklo_epi16(reg, reg); + reg = _mm_unpacklo_epi32(reg, reg); +#endif + + // Test if bits of interest are set + reg = _mm_and_si128(reg, bit_mask); + + // Now test if those bits are set, by comparing to zero. So the + // result will be that bytes where bits are set will be at 0, and + // ones where they are cleared will be at 0xFF. So the inverse of + // the end result we want! + reg = _mm_cmpeq_epi8(reg, zero); + + // Invert the result + reg = _mm_andnot_si128(reg, all_ones); + + _mm_storeu_si128(reinterpret_cast<__m128i *>(pabyOutput), reg); + + pabyOutput += SSE_REG_SIZE; + + // Right-shift of 2 bytes + reg_ori = _mm_bsrli_si128(reg_ori, NUM_PROCESSED_BYTES_PER_REG); + } + } + +#endif // HAVE_SSE2 + + for (; iByte < nInputWholeBytes; ++iByte) + { + ExpandEightPackedBitsToByteAt255(pabyInput[iByte], pabyOutput); + pabyOutput += 8; + } + for (int iBit = 0; iBit < static_cast(nInputBits % 8); ++iBit) + { + *pabyOutput = ExtractBitAndConvertTo255(pabyInput[iByte], 7 - iBit); + ++pabyOutput; + } +} + +/************************************************************************/ +/* ExpandEightPackedBitsToByteAt1() */ +/************************************************************************/ + +static inline void ExpandEightPackedBitsToByteAt1(GByte byVal, + GByte abyOutput[8]) +{ + abyOutput[0] = (byVal >> 7) & 0x1; + abyOutput[1] = (byVal >> 6) & 0x1; + abyOutput[2] = (byVal >> 5) & 0x1; + abyOutput[3] = (byVal >> 4) & 0x1; + abyOutput[4] = (byVal >> 3) & 0x1; + abyOutput[5] = (byVal >> 2) & 0x1; + abyOutput[6] = (byVal >> 1) & 0x1; + abyOutput[7] = (byVal >> 0) & 0x1; +} + +/************************************************************************/ +/* GDALExpandPackedBitsToByteAt0Or1() */ +/************************************************************************/ + +/** Expand packed-bits (ordered from most-significant bit to least one) + into a byte each, where a bit at 0 is expanded to a byte at 0, and a bit + at 1 to a byte at 1. + + The function does (in a possibly more optimized way) the following: + \code{.cpp} + for (size_t i = 0; i < nInputBits; ++i ) + { + pabyOutput[i] = (pabyInput[i / 8] & (1 << (7 - (i % 8)))) ? 1 : 0; + } + \endcode + + @param pabyInput Input array of (nInputBits + 7) / 8 bytes. + @param pabyOutput Output array of nInputBits bytes. + @param nInputBits Number of valid bits in pabyInput. + + @since 3.11 +*/ + +void GDALExpandPackedBitsToByteAt0Or1(const GByte *CPL_RESTRICT pabyInput, + GByte *CPL_RESTRICT pabyOutput, + size_t nInputBits) +{ + const size_t nInputWholeBytes = nInputBits / 8; + size_t iByte = 0; + for (; iByte < nInputWholeBytes; ++iByte) + { + ExpandEightPackedBitsToByteAt1(pabyInput[iByte], pabyOutput); + pabyOutput += 8; + } + for (int iBit = 0; iBit < static_cast(nInputBits % 8); ++iBit) + { + *pabyOutput = (pabyInput[iByte] >> (7 - iBit)) & 0x1; + ++pabyOutput; + } +} From 4b1e8724936f18ec4d7c0fa5914a23e625115831 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Jan 2025 17:43:13 +0100 Subject: [PATCH 06/14] GTiff: use GDALExpandPackedBitsToByteAt0Or255() and GDALExpandPackedBitsToByteAt0Or1() --- frmts/gtiff/gtiffoddbitsband.cpp | 91 ++++---------------------------- 1 file changed, 9 insertions(+), 82 deletions(-) diff --git a/frmts/gtiff/gtiffoddbitsband.cpp b/frmts/gtiff/gtiffoddbitsband.cpp index dc69116704d3..5518ba3a7149 100644 --- a/frmts/gtiff/gtiffoddbitsband.cpp +++ b/frmts/gtiff/gtiffoddbitsband.cpp @@ -515,60 +515,6 @@ CPLErr GTiffOddBitsBand::IWriteBlock(int nBlockXOff, int nBlockYOff, /* IReadBlock() */ /************************************************************************/ -static void ExpandPacked8ToByte1(const GByte *const CPL_RESTRICT pabySrc, - GByte *const CPL_RESTRICT pabyDest, - GPtrDiff_t nBytes) -{ - for (decltype(nBytes) i = 0, j = 0; i < nBytes; i++, j += 8) - { - const GByte byVal = pabySrc[i]; - pabyDest[j + 0] = (byVal >> 7) & 0x1; - pabyDest[j + 1] = (byVal >> 6) & 0x1; - pabyDest[j + 2] = (byVal >> 5) & 0x1; - pabyDest[j + 3] = (byVal >> 4) & 0x1; - pabyDest[j + 4] = (byVal >> 3) & 0x1; - pabyDest[j + 5] = (byVal >> 2) & 0x1; - pabyDest[j + 6] = (byVal >> 1) & 0x1; - pabyDest[j + 7] = (byVal >> 0) & 0x1; - } -} - -#if defined(__GNUC__) || defined(_MSC_VER) -// Signedness of char implementation dependent, so be explicit. -// Assumes 2-complement integer types and sign extension of right shifting -// GCC guarantees such: -// https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation -static inline GByte ExtractBitAndConvertTo255(GByte byVal, int nBit) -{ - return static_cast(static_cast(byVal << (7 - nBit)) >> - 7); -} -#else -// Portable way -static inline GByte ExtractBitAndConvertTo255(GByte byVal, int nBit) -{ - return (byVal & (1 << nBit)) ? 255 : 0; -} -#endif - -static void ExpandPacked8ToByte255(const GByte *const CPL_RESTRICT pabySrc, - GByte *const CPL_RESTRICT pabyDest, - GPtrDiff_t nBytes) -{ - for (decltype(nBytes) i = 0, j = 0; i < nBytes; i++, j += 8) - { - const GByte byVal = pabySrc[i]; - pabyDest[j + 0] = ExtractBitAndConvertTo255(byVal, 7); - pabyDest[j + 1] = ExtractBitAndConvertTo255(byVal, 6); - pabyDest[j + 2] = ExtractBitAndConvertTo255(byVal, 5); - pabyDest[j + 3] = ExtractBitAndConvertTo255(byVal, 4); - pabyDest[j + 4] = ExtractBitAndConvertTo255(byVal, 3); - pabyDest[j + 5] = ExtractBitAndConvertTo255(byVal, 2); - pabyDest[j + 6] = ExtractBitAndConvertTo255(byVal, 1); - pabyDest[j + 7] = ExtractBitAndConvertTo255(byVal, 0); - } -} - CPLErr GTiffOddBitsBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) @@ -608,43 +554,24 @@ CPLErr GTiffOddBitsBand::IReadBlock(int nBlockXOff, int nBlockYOff, (m_poGDS->nBands == 1 || m_poGDS->m_nPlanarConfig == PLANARCONFIG_SEPARATE)) { - /* -------------------------------------------------------------------- - */ - /* Translate 1bit data to eight bit. */ - /* -------------------------------------------------------------------- - */ - GPtrDiff_t iDstOffset = 0; - const GByte *const CPL_RESTRICT m_pabyBlockBuf = - m_poGDS->m_pabyBlockBuf; + // Translate 1bit data to eight bit. + const GByte *CPL_RESTRICT pabySrc = m_poGDS->m_pabyBlockBuf; GByte *CPL_RESTRICT pabyDest = static_cast(pImage); for (int iLine = 0; iLine < nBlockYSize; ++iLine) { - GPtrDiff_t iSrcOffsetByte = - static_cast((nBlockXSize + 7) >> 3) * iLine; - - if (!m_poGDS->m_bPromoteTo8Bits) + if (m_poGDS->m_bPromoteTo8Bits) { - ExpandPacked8ToByte1(m_pabyBlockBuf + iSrcOffsetByte, - pabyDest + iDstOffset, nBlockXSize / 8); + GDALExpandPackedBitsToByteAt0Or255(pabySrc, pabyDest, + nBlockXSize); } else { - ExpandPacked8ToByte255(m_pabyBlockBuf + iSrcOffsetByte, - pabyDest + iDstOffset, nBlockXSize / 8); - } - GPtrDiff_t iSrcOffsetBit = (iSrcOffsetByte + nBlockXSize / 8) * 8; - iDstOffset += nBlockXSize & ~0x7; - const GByte bSetVal = m_poGDS->m_bPromoteTo8Bits ? 255 : 1; - for (int iPixel = nBlockXSize & ~0x7; iPixel < nBlockXSize; - ++iPixel, ++iSrcOffsetBit) - { - if (m_pabyBlockBuf[iSrcOffsetBit >> 3] & - (0x80 >> (iSrcOffsetBit & 0x7))) - static_cast(pImage)[iDstOffset++] = bSetVal; - else - static_cast(pImage)[iDstOffset++] = 0; + GDALExpandPackedBitsToByteAt0Or1(pabySrc, pabyDest, + nBlockXSize); } + pabySrc += (nBlockXSize + 7) / 8; + pabyDest += nBlockXSize; } } /* -------------------------------------------------------------------- */ From 4bb4f4c97bd1b5fc97152bc2c3b2dc3c1d9cfe32 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Jan 2025 17:43:22 +0100 Subject: [PATCH 07/14] LIBERTIFF: use GDALExpandPackedBitsToByteAt0Or255() and GDALExpandPackedBitsToByteAt0Or1() --- frmts/libertiff/libertiffdataset.cpp | 64 +++------------------------- 1 file changed, 6 insertions(+), 58 deletions(-) diff --git a/frmts/libertiff/libertiffdataset.cpp b/frmts/libertiff/libertiffdataset.cpp index 6e88b572423b..9bfb72ec90e4 100644 --- a/frmts/libertiff/libertiffdataset.cpp +++ b/frmts/libertiff/libertiffdataset.cpp @@ -1166,28 +1166,6 @@ FloatingPointHorizPredictorDecode(std::vector &tmpBuffer, return true; } -/************************************************************************/ -/* ExtractBitAndConvertTo255() */ -/************************************************************************/ - -#if defined(__GNUC__) || defined(_MSC_VER) -// Signedness of char implementation dependent, so be explicit. -// Assumes 2-complement integer types and sign extension of right shifting -// GCC guarantees such: -// https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation -static inline GByte ExtractBitAndConvertTo255(GByte byVal, int nBit) -{ - return static_cast(static_cast(byVal << (7 - nBit)) >> - 7); -} -#else -// Portable way -static inline GByte ExtractBitAndConvertTo255(GByte byVal, int nBit) -{ - return (byVal & (1 << nBit)) ? 255 : 0; -} -#endif - /************************************************************************/ /* ReadBlock() */ /************************************************************************/ @@ -1582,51 +1560,21 @@ bool LIBERTIFFDataset::ReadBlock(GByte *pabyBlockData, int nBlockXOff, if (m_image->bitsPerSample() == 1) { const GByte *CPL_RESTRICT pabySrc = abyDecompressedStrile.data(); - const GByte val = m_bExpand1To255 ? 255 : 1; GByte *CPL_RESTRICT pabyDst = bufferForOneBitExpansion.data(); for (int iY = 0; iY < nBlockActualYSize; ++iY) { - int iX = 0; if (m_bExpand1To255) { - for (; iX + 7 < nBlockXSize; - iX += 8, ++pabySrc, pabyDst += 8) - { - const GByte srcByte = *pabySrc; - pabyDst[0] = ExtractBitAndConvertTo255(srcByte, 7); - pabyDst[1] = ExtractBitAndConvertTo255(srcByte, 6); - pabyDst[2] = ExtractBitAndConvertTo255(srcByte, 5); - pabyDst[3] = ExtractBitAndConvertTo255(srcByte, 4); - pabyDst[4] = ExtractBitAndConvertTo255(srcByte, 3); - pabyDst[5] = ExtractBitAndConvertTo255(srcByte, 2); - pabyDst[6] = ExtractBitAndConvertTo255(srcByte, 1); - pabyDst[7] = ExtractBitAndConvertTo255(srcByte, 0); - } + GDALExpandPackedBitsToByteAt0Or255(pabySrc, pabyDst, + nBlockXSize); } else { - for (; iX + 7 < nBlockXSize; - iX += 8, ++pabySrc, pabyDst += 8) - { - const int srcByte = *pabySrc; - pabyDst[0] = (srcByte >> 7) & 1; - pabyDst[1] = (srcByte >> 6) & 1; - pabyDst[2] = (srcByte >> 5) & 1; - pabyDst[3] = (srcByte >> 4) & 1; - pabyDst[4] = (srcByte >> 3) & 1; - pabyDst[5] = (srcByte >> 2) & 1; - pabyDst[6] = (srcByte >> 1) & 1; - pabyDst[7] = (srcByte >> 0) & 1; - } - } - if (iX < nBlockXSize) - { - for (; iX < nBlockXSize; ++iX, ++pabyDst) - { - *pabyDst = (*pabySrc & (0x80 >> (iX % 8))) ? val : 0; - } - ++pabySrc; + GDALExpandPackedBitsToByteAt0Or1(pabySrc, pabyDst, + nBlockXSize); } + pabySrc += (nBlockXSize + 7) / 8; + pabyDst += nBlockXSize; } std::swap(abyDecompressedStrile, bufferForOneBitExpansion); From 60adea252305fa61d7e9bdd6f0bd189a35760435 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Jan 2025 19:25:45 +0100 Subject: [PATCH 08/14] SWIG: stricter type use --- swig/include/Dataset.i | 12 +++++++++--- swig/include/MultiDimensional.i | 2 +- swig/include/Operations.i | 6 ++++-- swig/include/gnm.i | 4 ++-- swig/include/ogr.i | 2 +- swig/include/osr.i | 8 ++++---- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/swig/include/Dataset.i b/swig/include/Dataset.i index fe268aacba30..83a18c0e5f6a 100644 --- a/swig/include/Dataset.i +++ b/swig/include/Dataset.i @@ -844,15 +844,18 @@ CPLErr AdviseRead( int xoff, int yoff, int xsize, int ysize, GDALProgressFunc callback = NULL, void* callback_data=NULL ) { - return GDALDatasetGetNextFeature( self, ppoBelongingLayer, pdfProgressPct, + OGRLayerH hLayer = NULL; + OGRFeatureShadow* feat = (OGRFeatureShadow*)GDALDatasetGetNextFeature( self, &hLayer, pdfProgressPct, callback, callback_data ); + *ppoBelongingLayer = (OGRLayerShadow*)hLayer; + return feat; } #else // FIXME: return layer %newobject GetNextFeature; OGRFeatureShadow* GetNextFeature() { - return GDALDatasetGetNextFeature( self, NULL, NULL, NULL, NULL ); + return (OGRFeatureShadow*)GDALDatasetGetNextFeature( self, NULL, NULL, NULL, NULL ); } #endif @@ -925,8 +928,11 @@ CPLErr AdviseRead( int xoff, int yoff, int xsize, int ysize, GDALProgressFunc callback = NULL, void* callback_data=NULL ) { - return GDALDatasetGetNextFeature( self, ppoBelongingLayer, pdfProgressPct, + OGRLayerH hLayer = NULL; + OGRFeatureShadow* feat = (OGRFeatureShadow*)GDALDatasetGetNextFeature( self, &hLayer, pdfProgressPct, callback, callback_data ); + *ppoBelongingLayer = (OGRLayerShadow*)hLayer; + return feat; } diff --git a/swig/include/MultiDimensional.i b/swig/include/MultiDimensional.i index ebfafeda3c44..8d4ac889ef0b 100644 --- a/swig/include/MultiDimensional.i +++ b/swig/include/MultiDimensional.i @@ -1030,7 +1030,7 @@ public: %newobject GetSpatialRef; OSRSpatialReferenceShadow *GetSpatialRef() { - return GDALMDArrayGetSpatialRef(self); + return (OSRSpatialReferenceShadow*) GDALMDArrayGetSpatialRef(self); } #endif diff --git a/swig/include/Operations.i b/swig/include/Operations.i index 10a6203fedc5..2fa89653c7f8 100644 --- a/swig/include/Operations.i +++ b/swig/include/Operations.i @@ -208,8 +208,9 @@ int RasterizeLayer( GDALDatasetShadow *dataset, return CE_Failure; } + OGRLayerH hLayer = (OGRLayerH)layer; eErr = GDALRasterizeLayers( dataset, bands, band_list, - 1, &layer, + 1, &hLayer, NULL, NULL, burn_values_list, options, callback, callback_data ); @@ -252,8 +253,9 @@ int RasterizeLayer( GDALDatasetShadow *dataset, return CE_Failure; } + OGRLayerH hLayer = (OGRLayerH)layer; eErr = GDALRasterizeLayers( dataset, bands, band_list, - 1, &layer, + 1, &hLayer, (GDALTransformerFunc) pfnTransformer, pTransformArg, burn_values_list, options, diff --git a/swig/include/gnm.i b/swig/include/gnm.i index 3e1148d5d253..a5eb970efefe 100644 --- a/swig/include/gnm.i +++ b/swig/include/gnm.i @@ -181,7 +181,7 @@ class GNMNetworkShadow : public GDALMajorObjectShadow %newobject GetFeatureByGlobalFID; OGRFeatureShadow *GetFeatureByGlobalFID (GNMGFID GFID) { - return GNMGetFeatureByGlobalFID(self, GFID); + return (OGRFeatureShadow*)GNMGetFeatureByGlobalFID(self, GFID); } %newobject GetPath; @@ -192,7 +192,7 @@ class GNMNetworkShadow : public GDALMajorObjectShadow GNMGraphAlgorithmType eAlgorithm, char **options = 0) { - return GNMGetPath(self, nStartFID, nEndFID, eAlgorithm, options); + return (OGRLayerShadow*)GNMGetPath(self, nStartFID, nEndFID, eAlgorithm, options); } CPLErr DisconnectAll() { diff --git a/swig/include/ogr.i b/swig/include/ogr.i index f540a1cd77f6..32ec24a34393 100644 --- a/swig/include/ogr.i +++ b/swig/include/ogr.i @@ -4047,7 +4047,7 @@ public: %newobject Value; OGRGeometryShadow* Value(double dfDistance) { - return OGR_G_Value(self, dfDistance); + return (OGRGeometryShadow*)OGR_G_Value(self, dfDistance); } %newobject Transform; diff --git a/swig/include/osr.i b/swig/include/osr.i index 231353b0f485..bbfe40eb160e 100644 --- a/swig/include/osr.i +++ b/swig/include/osr.i @@ -1165,7 +1165,7 @@ public: %newobject ConvertToOtherProjection; OSRSpatialReferenceShadow* ConvertToOtherProjection(const char* other_projection, char **options = NULL) { - return OSRConvertToOtherProjection(self, other_projection, options); + return (OSRSpatialReferenceShadow*)OSRConvertToOtherProjection(self, other_projection, options); } %clear const char* name; @@ -1246,7 +1246,7 @@ public: OSRCoordinateTransformationShadow( OSRSpatialReferenceShadow *src, OSRSpatialReferenceShadow *dst, OGRCoordinateTransformationOptions* options ) { return (OSRCoordinateTransformationShadow*) - options ? OCTNewCoordinateTransformationEx( src, dst, options ) : OCTNewCoordinateTransformation(src, dst); + (options ? OCTNewCoordinateTransformationEx( src, dst, options ) : OCTNewCoordinateTransformation(src, dst)); } ~OSRCoordinateTransformationShadow() { @@ -1255,7 +1255,7 @@ public: %newobject GetInverse; OSRCoordinateTransformationShadow* GetInverse() { - return OCTGetInverse(self); + return (OSRCoordinateTransformationShadow*) OCTGetInverse(self); } // Need to apply argin typemap second so the numinputs=1 version gets applied @@ -1399,7 +1399,7 @@ void TransformBounds( %inline %{ OSRCoordinateTransformationShadow *CreateCoordinateTransformation( OSRSpatialReferenceShadow *src, OSRSpatialReferenceShadow *dst, OGRCoordinateTransformationOptions* options = NULL ) { return (OSRCoordinateTransformationShadow*) - options ? OCTNewCoordinateTransformationEx( src, dst, options ) : OCTNewCoordinateTransformation(src, dst); + (options ? OCTNewCoordinateTransformationEx( src, dst, options ) : OCTNewCoordinateTransformation(src, dst)); } %} From 6465523a7b381a34a02747bbb4ad7505a6fcc71c Mon Sep 17 00:00:00 2001 From: Howard Butler Date: Wed, 8 Jan 2025 16:45:44 -0600 Subject: [PATCH 09/14] explicitly set CMAKE_CXX_SCAN_FOR_MODULES=0 --- cmake/helpers/GdalCAndCXXStandards.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/helpers/GdalCAndCXXStandards.cmake b/cmake/helpers/GdalCAndCXXStandards.cmake index 6e2480cab02c..8251f1e95bbc 100644 --- a/cmake/helpers/GdalCAndCXXStandards.cmake +++ b/cmake/helpers/GdalCAndCXXStandards.cmake @@ -8,3 +8,8 @@ if (NOT CMAKE_C_STANDARD) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) endif() + +# explicitly tell CMake not to do module +# dependency scanning +# https://discourse.cmake.org/t/cmake-3-28-cmake-cxx-compiler-clang-scan-deps-notfound-not-found/9244/3 +set(CMAKE_CXX_SCAN_FOR_MODULES 0) From b5389d243aeb0aa72ae9ab085b6404c16dd0bc76 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 8 Jan 2025 19:26:13 +0100 Subject: [PATCH 10/14] Add a gdal_fwd.h public header with forward definitions of GDAL/OGR/OSR C opaque types Fixes #11611 --- doc/source/api/gdal_fwd.rst | 13 +++ doc/source/api/index.rst | 1 + gcore/CMakeLists.txt | 1 + gcore/gdal.h | 49 +------- gcore/gdal_fwd.h | 216 ++++++++++++++++++++++++++++++++++++ gnm/gnm_api.h | 5 +- ogr/ogr_api.h | 91 +-------------- ogr/ogr_feature.h | 27 +---- ogr/ogr_geometry.h | 12 +- ogr/ogr_srs_api.h | 21 +--- 10 files changed, 239 insertions(+), 197 deletions(-) create mode 100644 doc/source/api/gdal_fwd.rst create mode 100644 gcore/gdal_fwd.h diff --git a/doc/source/api/gdal_fwd.rst b/doc/source/api/gdal_fwd.rst new file mode 100644 index 000000000000..7cb72b5c72c8 --- /dev/null +++ b/doc/source/api/gdal_fwd.rst @@ -0,0 +1,13 @@ +.. + The documentation displayed on this page is automatically generated from + Doxygen comments using the Breathe extension. Edits to the documentation + can be made by making changes in the appropriate .cpp files. + +.. _gdal_fwd: + +================================================================================ +gdal_fwd.h: Forward definitions of GDAL/OGR/OSR C handle types. +================================================================================ + +.. doxygenfile:: gdal_fwd.h + :project: api diff --git a/doc/source/api/index.rst b/doc/source/api/index.rst index 652fdd2e39df..8cad65630f72 100644 --- a/doc/source/api/index.rst +++ b/doc/source/api/index.rst @@ -16,6 +16,7 @@ API :maxdepth: 1 cpl + gdal_fwd raster_c_api vector_c_api gdal_alg diff --git a/gcore/CMakeLists.txt b/gcore/CMakeLists.txt index 044a4c667196..325e34d1049a 100644 --- a/gcore/CMakeLists.txt +++ b/gcore/CMakeLists.txt @@ -213,6 +213,7 @@ target_public_header( HEADERS ${CMAKE_CURRENT_BINARY_DIR}/gdal_version_full/gdal_version.h gdal.h + gdal_fwd.h gdalalgorithm.h gdaljp2metadata.h gdaljp2abstractdataset.h diff --git a/gcore/gdal.h b/gcore/gdal.h index 8a8f414a826d..8f83b5d3bc9b 100644 --- a/gcore/gdal.h +++ b/gcore/gdal.h @@ -24,6 +24,7 @@ #if defined(GDAL_COMPILATION) #define DO_NOT_DEFINE_GDAL_DATE_NAME #endif +#include "gdal_fwd.h" #include "gdal_version.h" #include "cpl_port.h" #include "cpl_error.h" @@ -365,36 +366,9 @@ const char CPL_DLL *GDALGetPaletteInterpretationName(GDALPaletteInterp); #endif /* -------------------------------------------------------------------- */ -/* Define handle types related to various internal classes. */ +/* Types, enumerations. */ /* -------------------------------------------------------------------- */ -/** Opaque type used for the C bindings of the C++ GDALMajorObject class */ -typedef void *GDALMajorObjectH; - -/** Opaque type used for the C bindings of the C++ GDALDataset class */ -typedef void *GDALDatasetH; - -/** Opaque type used for the C bindings of the C++ GDALRasterBand class */ -typedef void *GDALRasterBandH; - -/** Opaque type used for the C bindings of the C++ GDALDriver class */ -typedef void *GDALDriverH; - -/** Opaque type used for the C bindings of the C++ GDALColorTable class */ -typedef void *GDALColorTableH; - -/** Opaque type used for the C bindings of the C++ GDALRasterAttributeTable - * class */ -typedef void *GDALRasterAttributeTableH; - -/** Opaque type used for the C bindings of the C++ GDALAsyncReader class */ -typedef void *GDALAsyncReaderH; - -/** Opaque type used for the C bindings of the C++ GDALRelationship class - * @since GDAL 3.6 - */ -typedef void *GDALRelationshipH; - /** Type to express pixel, line or band spacing. Signed 64 bit integer. */ typedef GIntBig GSpacing; @@ -422,19 +396,6 @@ typedef enum GEDTST_JSON } GDALExtendedDataTypeSubType; -/** Opaque type for C++ GDALExtendedDataType */ -typedef struct GDALExtendedDataTypeHS *GDALExtendedDataTypeH; -/** Opaque type for C++ GDALEDTComponent */ -typedef struct GDALEDTComponentHS *GDALEDTComponentH; -/** Opaque type for C++ GDALGroup */ -typedef struct GDALGroupHS *GDALGroupH; -/** Opaque type for C++ GDALMDArray */ -typedef struct GDALMDArrayHS *GDALMDArrayH; -/** Opaque type for C++ GDALAttribute */ -typedef struct GDALAttributeHS *GDALAttributeH; -/** Opaque type for C++ GDALDimension */ -typedef struct GDALDimensionHS *GDALDimensionH; - /* ==================================================================== */ /* Registration/driver related. */ /* ==================================================================== */ @@ -1435,12 +1396,6 @@ bool CPL_DLL GDALDatasetSetQueryLoggerFunc( /* Informational utilities about subdatasets in file names */ /* ==================================================================== */ -/** - * Opaque type used for the C bindings of the C++ GDALSubdatasetInfo class - * @since GDAL 3.8 -*/ -typedef struct GDALSubdatasetInfo *GDALSubdatasetInfoH; - /** * @brief Returns a new GDALSubdatasetInfo object with methods to extract * and manipulate subdataset information. diff --git a/gcore/gdal_fwd.h b/gcore/gdal_fwd.h new file mode 100644 index 000000000000..f8c5a179fe7b --- /dev/null +++ b/gcore/gdal_fwd.h @@ -0,0 +1,216 @@ +/****************************************************************************** + * + * Project: GDAL + * Purpose: Forward definitions of GDAL/OGR/OSR C handle types. + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 1998, 2002 Frank Warmerdam + * Copyright (c) 2007-2024, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#ifndef GDAL_FWD_H_INCLUDED +#define GDAL_FWD_H_INCLUDED + +/* clang-format off */ +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \file gdal_fwd.h + * + * Forward definitions of GDAL/OGR/OSR C handle types. + * + * Users should only rely on the type name, and not its definition, which + * could change in later versions. + * + * @since GDAL 3.11 + */ + +/*! +\section gdal_fwd_raster Raster related types. +*/ + +/** Opaque type used for the C bindings of the C++ GDALMajorObject class */ +typedef void *GDALMajorObjectH; + +/** Opaque type used for the C bindings of the C++ GDALDataset class */ +typedef void *GDALDatasetH; + +/** Opaque type used for the C bindings of the C++ GDALRasterBand class */ +typedef void *GDALRasterBandH; + +/** Opaque type used for the C bindings of the C++ GDALDriver class */ +typedef void *GDALDriverH; + +/** Opaque type used for the C bindings of the C++ GDALColorTable class */ +typedef void *GDALColorTableH; + +/** Opaque type used for the C bindings of the C++ GDALRasterAttributeTable + * class */ +typedef void *GDALRasterAttributeTableH; + +/** Opaque type used for the C bindings of the C++ GDALAsyncReader class */ +typedef void *GDALAsyncReaderH; + +/** Opaque type used for the C bindings of the C++ GDALRelationship class + * @since GDAL 3.6 + */ +typedef void *GDALRelationshipH; + +/** Opaque type for C++ GDALExtendedDataType */ +typedef struct GDALExtendedDataTypeHS *GDALExtendedDataTypeH; +/** Opaque type for C++ GDALEDTComponent */ +typedef struct GDALEDTComponentHS *GDALEDTComponentH; +/** Opaque type for C++ GDALGroup */ +typedef struct GDALGroupHS *GDALGroupH; +/** Opaque type for C++ GDALMDArray */ +typedef struct GDALMDArrayHS *GDALMDArrayH; +/** Opaque type for C++ GDALAttribute */ +typedef struct GDALAttributeHS *GDALAttributeH; +/** Opaque type for C++ GDALDimension */ +typedef struct GDALDimensionHS *GDALDimensionH; + +/** + * Opaque type used for the C bindings of the C++ GDALSubdatasetInfo class + * @since GDAL 3.8 +*/ +typedef struct GDALSubdatasetInfo *GDALSubdatasetInfoH; + +/*! +\section gdal_fwd_geometry Geometry related types. +*/ + +#if defined(GDAL_DEBUG) +/** Opaque type for a geometry */ +typedef struct OGRGeometryHS *OGRGeometryH; +#else +/** Opaque type for a geometry */ +typedef void *OGRGeometryH; +#endif + +/** Opaque type for a geometry transformer. */ +typedef struct OGRGeomTransformer *OGRGeomTransformerH; + +/** Opaque type for OGRGeomCoordinatePrecision */ +typedef struct OGRGeomCoordinatePrecision *OGRGeomCoordinatePrecisionH; + +/** Opaque type for WKB export options */ +typedef struct OGRwkbExportOptions OGRwkbExportOptions; + +/** Opaque type for a prepared geometry */ +typedef struct _OGRPreparedGeometry *OGRPreparedGeometryH; + +/*! +\section gdal_fwd_field Attribute field, geometry field and layer definitions. +*/ + +#if defined(GDAL_DEBUG) +/** Opaque type for a field definition (OGRFieldDefn) */ +typedef struct OGRFieldDefnHS *OGRFieldDefnH; +/** Opaque type for a feature definition (OGRFeatureDefn) */ +typedef struct OGRFeatureDefnHS *OGRFeatureDefnH; +#else +/** Opaque type for a field definition (OGRFieldDefn) */ +typedef void *OGRFieldDefnH; +/** Opaque type for a feature definition (OGRFeatureDefn) */ +typedef void *OGRFeatureDefnH; +#endif + +/** Opaque type for a geometry field definition (OGRGeomFieldDefn) */ +typedef struct OGRGeomFieldDefnHS *OGRGeomFieldDefnH; + +/** Opaque type for a field domain definition (OGRFieldDomain) */ +typedef struct OGRFieldDomainHS *OGRFieldDomainH; + +/*! +\section gdal_fwd_feature Vector feature type. +*/ +#if defined(GDAL_DEBUG) +/** Opaque type for a feature (OGRFeature) */ +typedef struct OGRFeatureHS *OGRFeatureH; +#else +/** Opaque type for a feature (OGRFeature) */ +typedef void *OGRFeatureH; +#endif + +/*! +\section gdal_fwd_layer Vector layer related types. +*/ + +#if defined(GDAL_DEBUG) +/** Opaque type for a layer (OGRLayer) */ +typedef struct OGRLayerHS *OGRLayerH; +/** Opaque type for a OGR datasource (OGRDataSource) (deprecated) */ +typedef struct OGRDataSourceHS *OGRDataSourceH; +/** Opaque type for a OGR driver (OGRSFDriver) (deprecated) */ +typedef struct OGRDriverHS *OGRSFDriverH; +#else +/** Opaque type for a layer (OGRLayer) */ +typedef void *OGRLayerH; +/** Opaque type for a OGR datasource (OGRDataSource) (deprecated) */ +typedef void *OGRDataSourceH; +/** Opaque type for a OGR driver (OGRSFDriver) (deprecated) */ +typedef void *OGRSFDriverH; +#endif + +/*! +\section gdal_fwd_style Vector styling related types. +*/ + +#if defined(GDAL_DEBUG) +/** Style manager opaque type */ +typedef struct OGRStyleMgrHS *OGRStyleMgrH; +/** Opaque type for a style table (OGRStyleTable) */ +typedef struct OGRStyleTableHS *OGRStyleTableH; +/** Style tool opaque type */ +typedef struct OGRStyleToolHS *OGRStyleToolH; +#else +/** Style manager opaque type */ +typedef void *OGRStyleMgrH; +/** Style tool opaque type */ +typedef void *OGRStyleToolH; +/** Opaque type for a style table (OGRStyleTable) */ +typedef void *OGRStyleTableH; +#endif + +/*! +\section gdal_fwd_crs CRS and coordinate transformation related types. +*/ + +#if defined(GDAL_DEBUG) +/** Opaque type for a spatial reference system */ +typedef struct OGRSpatialReferenceHS *OGRSpatialReferenceH; +/** Opaque type for a coordinate transformation object */ +typedef struct OGRCoordinateTransformationHS *OGRCoordinateTransformationH; +#else +/** Opaque type for a spatial reference system */ +typedef void *OGRSpatialReferenceH; +/** Opaque type for a coordinate transformation object */ +typedef void *OGRCoordinateTransformationH; +#endif + +/** Coordinate transformation options. */ +typedef struct OGRCoordinateTransformationOptions + *OGRCoordinateTransformationOptionsH; + +/*! +\section gdal_fwd_gnm GNM (Geography Network Models) related types. +*/ + +/** Opaque type for a GNMNetwork */ +typedef void *GNMNetworkH; + +/** Opaque type for a GNMGenericNetwork */ +typedef void *GNMGenericNetworkH; + +#ifdef __cplusplus +} +#endif +/* clang-format on */ + +#endif // GDAL_FWD_H_INCLUDED diff --git a/gnm/gnm_api.h b/gnm/gnm_api.h index ef6955cc1603..65d2ec731416 100644 --- a/gnm/gnm_api.h +++ b/gnm/gnm_api.h @@ -33,10 +33,9 @@ #include "gnm.h" -CPL_C_START +#include "gdal_fwd.h" -typedef void *GNMNetworkH; -typedef void *GNMGenericNetworkH; +CPL_C_START const char CPL_DLL *CPL_STDCALL GNMGetName(GNMNetworkH hNet); diff --git a/ogr/ogr_api.h b/ogr/ogr_api.h index f2ddd1764315..94d1f2c6825d 100644 --- a/ogr/ogr_api.h +++ b/ogr/ogr_api.h @@ -26,6 +26,7 @@ #include "cpl_progress.h" #include "cpl_minixml.h" #include "ogr_core.h" +#include "gdal_fwd.h" #include #include @@ -38,36 +39,6 @@ bool CPL_DLL OGRGetGEOSVersion(int *pnMajor, int *pnMinor, int *pnPatch); /* -------------------------------------------------------------------- */ /* Geometry related functions (ogr_geometry.h) */ /* -------------------------------------------------------------------- */ -#ifndef DEFINEH_OGRGeometryH -/*! @cond Doxygen_Suppress */ -#define DEFINEH_OGRGeometryH -/*! @endcond */ -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRGeometryHS *OGRGeometryH; -#else -/** Opaque type for a geometry */ -typedef void *OGRGeometryH; -#endif -#endif /* DEFINEH_OGRGeometryH */ - -#ifndef DEFINED_OGRSpatialReferenceH -/*! @cond Doxygen_Suppress */ -#define DEFINED_OGRSpatialReferenceH -/*! @endcond */ - -#ifndef DOXYGEN_XML -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRSpatialReferenceHS *OGRSpatialReferenceH; -typedef struct OGRCoordinateTransformationHS *OGRCoordinateTransformationH; -#else -/** Opaque type for a spatial reference system */ -typedef void *OGRSpatialReferenceH; -/** Opaque type for a coordinate transformation object */ -typedef void *OGRCoordinateTransformationH; -#endif -#endif - -#endif /* DEFINED_OGRSpatialReferenceH */ struct _CPLXMLNode; @@ -76,9 +47,6 @@ struct _CPLXMLNode; /** Value for a unknown coordinate precision. */ #define OGR_GEOM_COORD_PRECISION_UNKNOWN 0 -/** Opaque type for OGRGeomCoordinatePrecision */ -typedef struct OGRGeomCoordinatePrecision *OGRGeomCoordinatePrecisionH; - OGRGeomCoordinatePrecisionH CPL_DLL OGRGeomCoordinatePrecisionCreate(void); void CPL_DLL OGRGeomCoordinatePrecisionDestroy(OGRGeomCoordinatePrecisionH); double CPL_DLL @@ -154,9 +122,6 @@ OGRErr CPL_DLL OGR_G_ExportToWkb(OGRGeometryH, OGRwkbByteOrder, OGRErr CPL_DLL OGR_G_ExportToIsoWkb(OGRGeometryH, OGRwkbByteOrder, unsigned char *); -/** Opaque type for WKB export options */ -typedef struct OGRwkbExportOptions OGRwkbExportOptions; - OGRwkbExportOptions CPL_DLL *OGRwkbExportOptionsCreate(void); void CPL_DLL OGRwkbExportOptionsDestroy(OGRwkbExportOptions *); void CPL_DLL OGRwkbExportOptionsSetByteOrder(OGRwkbExportOptions *, @@ -208,8 +173,6 @@ OGRSpatialReferenceH CPL_DLL OGR_G_GetSpatialReference(OGRGeometryH); OGRErr CPL_DLL OGR_G_Transform(OGRGeometryH, OGRCoordinateTransformationH); OGRErr CPL_DLL OGR_G_TransformTo(OGRGeometryH, OGRSpatialReferenceH); -/** Opaque type for a geometry transformer. */ -typedef struct OGRGeomTransformer *OGRGeomTransformerH; OGRGeomTransformerH CPL_DLL OGR_GeomTransformer_Create(OGRCoordinateTransformationH, CSLConstList papszOptions) CPL_WARN_UNUSED_RESULT; @@ -376,9 +339,6 @@ int CPL_DLL OGRGetGenerate_DB2_V72_BYTE_ORDER(void); void CPL_DLL OGRSetNonLinearGeometriesEnabledFlag(int bFlag); int CPL_DLL OGRGetNonLinearGeometriesEnabledFlag(void); -/** Opaque type for a prepared geometry */ -typedef struct _OGRPreparedGeometry *OGRPreparedGeometryH; - int CPL_DLL OGRHasPreparedGeometrySupport(void); OGRPreparedGeometryH CPL_DLL OGRCreatePreparedGeometry(OGRGeometryH hGeom); void CPL_DLL OGRDestroyPreparedGeometry(OGRPreparedGeometryH hPreparedGeom); @@ -391,32 +351,6 @@ int CPL_DLL OGRPreparedGeometryContains(OGRPreparedGeometryH hPreparedGeom, /* Feature related (ogr_feature.h) */ /* -------------------------------------------------------------------- */ -#ifndef DEFINE_OGRFeatureH -/*! @cond Doxygen_Suppress */ -#define DEFINE_OGRFeatureH -/*! @endcond */ -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRFieldDefnHS *OGRFieldDefnH; -typedef struct OGRFeatureDefnHS *OGRFeatureDefnH; -typedef struct OGRFeatureHS *OGRFeatureH; -typedef struct OGRStyleTableHS *OGRStyleTableH; -#else -/** Opaque type for a field definition (OGRFieldDefn) */ -typedef void *OGRFieldDefnH; -/** Opaque type for a feature definition (OGRFeatureDefn) */ -typedef void *OGRFeatureDefnH; -/** Opaque type for a feature (OGRFeature) */ -typedef void *OGRFeatureH; -/** Opaque type for a style table (OGRStyleTable) */ -typedef void *OGRStyleTableH; -#endif -/** Opaque type for a geometry field definition (OGRGeomFieldDefn) */ -typedef struct OGRGeomFieldDefnHS *OGRGeomFieldDefnH; - -/** Opaque type for a field domain definition (OGRFieldDomain) */ -typedef struct OGRFieldDomainHS *OGRFieldDomainH; -#endif /* DEFINE_OGRFeatureH */ - /* OGRFieldDefn */ OGRFieldDefnH CPL_DLL OGR_Fld_Create(const char *, @@ -668,19 +602,6 @@ const char CPL_DLL *OGR_GlobFldDomain_GetGlob(OGRFieldDomainH); /* ogrsf_frmts.h */ /* -------------------------------------------------------------------- */ -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRLayerHS *OGRLayerH; -typedef struct OGRDataSourceHS *OGRDataSourceH; -typedef struct OGRDriverHS *OGRSFDriverH; -#else -/** Opaque type for a layer (OGRLayer) */ -typedef void *OGRLayerH; -/** Opaque type for a OGR datasource (OGRDataSource) */ -typedef void *OGRDataSourceH; -/** Opaque type for a OGR driver (OGRSFDriver) */ -typedef void *OGRSFDriverH; -#endif - /* OGRLayer */ const char CPL_DLL *OGR_L_GetName(OGRLayerH); @@ -950,16 +871,6 @@ void CPL_DLL OGRCleanupAll(void); /* ogrsf_featurestyle.h */ /* -------------------------------------------------------------------- */ -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRStyleMgrHS *OGRStyleMgrH; -typedef struct OGRStyleToolHS *OGRStyleToolH; -#else -/** Style manager opaque type */ -typedef void *OGRStyleMgrH; -/** Style tool opaque type */ -typedef void *OGRStyleToolH; -#endif - /* OGRStyleMgr */ OGRStyleMgrH CPL_DLL OGR_SM_Create(OGRStyleTableH hStyleTable) diff --git a/ogr/ogr_feature.h b/ogr/ogr_feature.h index cf8c3a84d6b3..b8004bc2ccec 100644 --- a/ogr/ogr_feature.h +++ b/ogr/ogr_feature.h @@ -15,6 +15,7 @@ #define OGR_FEATURE_H_INCLUDED #include "cpl_atomic_ops.h" +#include "gdal_fwd.h" #include "ogr_featurestyle.h" #include "ogr_geometry.h" #include "ogr_geomcoordinateprecision.h" @@ -32,32 +33,6 @@ * Simple feature classes. */ -#ifndef DEFINE_OGRFeatureH -/*! @cond Doxygen_Suppress */ -#define DEFINE_OGRFeatureH -/*! @endcond */ -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRFieldDefnHS *OGRFieldDefnH; -typedef struct OGRFeatureDefnHS *OGRFeatureDefnH; -typedef struct OGRFeatureHS *OGRFeatureH; -typedef struct OGRStyleTableHS *OGRStyleTableH; -#else -/** Opaque type for a field definition (OGRFieldDefn) */ -typedef void *OGRFieldDefnH; -/** Opaque type for a feature definition (OGRFeatureDefn) */ -typedef void *OGRFeatureDefnH; -/** Opaque type for a feature (OGRFeature) */ -typedef void *OGRFeatureH; -/** Opaque type for a style table (OGRStyleTable) */ -typedef void *OGRStyleTableH; -#endif -/** Opaque type for a geometry field definition (OGRGeomFieldDefn) */ -typedef struct OGRGeomFieldDefnHS *OGRGeomFieldDefnH; - -/** Opaque type for a field domain definition (OGRFieldDomain) */ -typedef struct OGRFieldDomainHS *OGRFieldDomainH; -#endif /* DEFINE_OGRFeatureH */ - class OGRStyleTable; /************************************************************************/ diff --git a/ogr/ogr_geometry.h b/ogr/ogr_geometry.h index 414d5d2829b4..a8d6c1145a09 100644 --- a/ogr/ogr_geometry.h +++ b/ogr/ogr_geometry.h @@ -17,6 +17,7 @@ #include "cpl_conv.h" #include "cpl_json.h" +#include "gdal_fwd.h" #include "ogr_core.h" #include "ogr_geomcoordinateprecision.h" #include "ogr_spatialref.h" @@ -32,17 +33,6 @@ * Simple feature geometry classes. */ -/*! @cond Doxygen_Suppress */ -#ifndef DEFINEH_OGRGeometryH -#define DEFINEH_OGRGeometryH -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRGeometryHS *OGRGeometryH; -#else -typedef void *OGRGeometryH; -#endif -#endif /* DEFINEH_OGRGeometryH */ -/*! @endcond */ - /// WKT Output formatting options. enum class OGRWktFormat { diff --git a/ogr/ogr_srs_api.h b/ogr/ogr_srs_api.h index d2c6a5386d12..7e477c88b049 100644 --- a/ogr/ogr_srs_api.h +++ b/ogr/ogr_srs_api.h @@ -18,6 +18,7 @@ #ifndef SWIG #include "ogr_core.h" +#include "gdal_fwd.h" CPL_C_START @@ -420,22 +421,6 @@ const char CPL_DLL *OSRAxisEnumToName(OGRAxisOrientation eOrientation); /* -------------------------------------------------------------------- */ /* C Wrappers for C++ objects and methods. */ /* -------------------------------------------------------------------- */ -#ifndef DEFINED_OGRSpatialReferenceH -/*! @cond Doxygen_Suppress */ -#define DEFINED_OGRSpatialReferenceH -/*! @endcond */ - -#if defined(DEBUG) || defined(GDAL_DEBUG) -typedef struct OGRSpatialReferenceHS *OGRSpatialReferenceH; -typedef struct OGRCoordinateTransformationHS *OGRCoordinateTransformationH; -#else -/** Opaque type for a Spatial Reference object */ -typedef void *OGRSpatialReferenceH; -/** Opaque type for a coordinate transformation object */ -typedef void *OGRCoordinateTransformationH; -#endif - -#endif void CPL_DLL OSRSetPROJSearchPaths(const char *const *papszPaths); char CPL_DLL **OSRGetPROJSearchPaths(void); @@ -1012,10 +997,6 @@ char CPL_DLL **OSRGetAuthorityListFromDatabase(void); OGRCoordinateTransformationH CPL_DLL CPL_STDCALL OCTNewCoordinateTransformation( OGRSpatialReferenceH hSourceSRS, OGRSpatialReferenceH hTargetSRS); -/** Coordinate transformation options. */ -typedef struct OGRCoordinateTransformationOptions - *OGRCoordinateTransformationOptionsH; - OGRCoordinateTransformationOptionsH CPL_DLL OCTNewCoordinateTransformationOptions(void); From 29ba8b7845b10b28dced4d29cf521a833d580449 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Jan 2025 03:16:29 +0100 Subject: [PATCH 11/14] =?UTF-8?q?Add=20a=20data/gdal=5Falgorithm.schema.js?= =?UTF-8?q?on=20to=20validate=20'gdal=20[command]=C2=A0[subcommand]=20--js?= =?UTF-8?q?on-usage'=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/alpine/build.sh | 9 ++ apps/CMakeLists.txt | 1 + apps/data/gdal_algorithm.schema.json | 198 +++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 apps/data/gdal_algorithm.schema.json diff --git a/.github/workflows/alpine/build.sh b/.github/workflows/alpine/build.sh index d20df60990d7..dc96272b428f 100755 --- a/.github/workflows/alpine/build.sh +++ b/.github/workflows/alpine/build.sh @@ -39,3 +39,12 @@ else echo "DeclareDeferredFOO() has NOT been run" exit 1 fi + +# +echo "Validating gdal --json-usage output" +apps/gdal --json-usage > out.json +export PYTHON_CMD=python3 +$PYTHON_CMD -m venv myvenv +source myvenv/bin/activate +$PYTHON_CMD -m pip install -U check-jsonschema +check-jsonschema --schemafile data/gdal_algorithm.schema.json out.json diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index e37a04fc79f1..47492b836e30 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -236,6 +236,7 @@ if (BUILD_APPS) endif () set(GDAL_DATA_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/data/gdal_algorithm.schema.json ${CMAKE_CURRENT_SOURCE_DIR}/data/gdalinfo_output.schema.json ${CMAKE_CURRENT_SOURCE_DIR}/data/gdalmdiminfo_output.schema.json ${CMAKE_CURRENT_SOURCE_DIR}/data/ogrinfo_output.schema.json diff --git a/apps/data/gdal_algorithm.schema.json b/apps/data/gdal_algorithm.schema.json new file mode 100644 index 000000000000..d19b04a9797f --- /dev/null +++ b/apps/data/gdal_algorithm.schema.json @@ -0,0 +1,198 @@ +{ + "$id": "https://gdal.org/gdal_algorithm.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Validate the output of the 'gdal [command] [subcommand] --json-usage'", + + "oneOf": [ + { + "$ref": "#/definitions/algorithm" + } + ], + + "definitions": { + + "algorithm": { + "type": "object", + "properties": { + "name": { + "type": "string", + "$comment": "Algorithm name. Normally always set, except for the top 'gdal' algorithm" + }, + "full_path": { + "type": "array", + "items": { + "type": "string" + }, + "$comment": "Full path to get to this algorithm. For example, for 'gdal raster info', this will be [\"gdal\", \"raster\", \"info\"]" + }, + "description": { + "type": "string", + "$comment": "One sentence description of the algorithm" + }, + "short_url": { + "type": "string", + "$comment": "URL to the help page, without the server. For example \"/programs/gdal_raster_info.html\"" + }, + "url": { + "type": "string", + "$comment": "Full URL to the help page. For example \"https://gdal.org/programs/gdal_raster_info.html\"" + }, + "sub_algorithms": { + "type": "array", + "items": { + "$ref": "#/definitions/algorithm" + }, + "$comment": "Names of the immediate sub-algorithm of this algorithm. For example, or 'gdal raster', this will be [\"info\", \"convert\", ...]" + }, + "input_arguments": { + "type": "array", + "items": { + "$ref": "#/definitions/argument" + }, + "$comment": "Input arguments of the algorithm" + }, + "output_arguments": { + "type": "array", + "items": { + "$ref": "#/definitions/argument" + }, + "$comment": "Output arguments of the algorithm, that is arguments that are set by the algorithm (typically an output dataset)" + }, + "input_output_arguments": { + "type": "array", + "items": { + "$ref": "#/definitions/argument" + }, + "$comment": "Arguments of the algorithm that are both read and written by it." + }, + "pipeline_algorithms": { + "type": "array", + "items": { + "$ref": "#/definitions/algorithm" + }, + "$comment": "For pipeline algorithm, description of the accepted step algorithms. Only present for such pipeline algorithms" + } + }, + "required": [ + "description", + "short_url", + "url", + "sub_algorithms", + "input_arguments", + "output_arguments", + "input_output_arguments" + ], + "additionalProperties": false + }, + + "argument": { + "type": "object", + "properties": { + "name": { + "type": "string", + "$comment": "Argument name" + }, + "type": { + "enum": [ + "boolean", + "integer", + "real", + "string", + "dataset", + "integer_list", + "real_list", + "string_list", + "dataset_list" + ], + "$comment": "Argument type" + }, + "description": { + "type": "string", + "$comment": "Argument description" + }, + "choices": { + "type": "array", + "items": { + "type": "string" + }, + "$comment": "Valid values for the argument, when it accepts an enumeration as a value" + }, + "default": { + "$comment": "Default value (optional)" + }, + "required": { + "type": "boolean", + "$comment": "Whether this argument is required" + }, + "packed_values_allowed": { + "type": "boolean", + "$comment": "For command-line specification, for multi-valued arguments (type: integer_list, real_list, string_list), whether comma-separated values are accepted. e.g. '--my-arg=first,second'" + }, + "repeated_arg_allowed": { + "type": "boolean", + "$comment": "For command-line specification, for multi-valued arguments (type: integer_list, real_list, string_list), whether several repetition of the argument are accepted. e.g. '--my-arg first --my-arg second'" + }, + "min_count": { + "type": "integer", + "$comment": "For multi-valued arguments (type: integer_list, real_list, string_list, dataset_list), minimum number of values" + }, + "max_count": { + "type": "integer", + "$comment": "For multi-valued arguments (type: integer_list, real_list, string_list, dataset_list), maximum number of values" + }, + "category": { + "type": "string", + "$comment": "Category of the argument. Common categories include \"Base\", \"Advanced\", \"Esoteric\", but algorithms may define their own categories." + }, + "mutual_exclusion_group": { + "type": "string", + "$comment": "Identifier shared by several arguments to mean they are mutually exclusive." + }, + "metadata": { + "type": "object", + "$comment": "Object whose keys are metadata item names. This is typically used for type=dataset arguments. e.g. \"metadata\":{ \"required_capabilities\":[ \"DCAP_RASTER\", \"DCAP_CREATECOPY\" ] }" + }, + "dataset_type": { + "type": "array", + "items": { + "enum": [ + "raster", + "vector", + "multidim_raster" + ] + }, + "$comment": "Type of accepted datasets. Only used for type=dataset or type=dataset_list." + }, + "input_flags": { + "type": "array", + "items": { + "enum": [ + "name", + "dataset" + ] + }, + "$comment": "Which fields of a dataset argument may be set, by the user, when the argument is dealt as an input dataset. Only used for type=dataset or type=dataset_list." + }, + "output_flags": { + "type": "array", + "items": { + "enum": [ + "name", + "dataset" + ] + }, + "$comment": "Which fields of a dataset argument may be set, by the algorithm, when the argument is dealt as an output dataset. Only used for type=dataset or type=dataset_list." + } + }, + "required": [ + "name", + "type", + "description", + "required", + "category" + ], + "additionalProperties": false + } + + } +} From fb37e019b4ff60dc8be48b34c0c945ddbaf4c24c Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Jan 2025 16:14:46 +0100 Subject: [PATCH 12/14] CPLErrorOnce()/CPLDebugOnce(): append a 'Further messages of this type will be suppressed' suffix text --- port/cpl_error.h | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/port/cpl_error.h b/port/cpl_error.h index 748d61555c5c..a4d8fcd0a862 100644 --- a/port/cpl_error.h +++ b/port/cpl_error.h @@ -126,21 +126,36 @@ void CPL_DLL CPLError(CPLErr eErrClass, CPLErrorNum err_no, CPL_FORMAT_STRING(const char *fmt), ...) CPL_PRINT_FUNC_FORMAT(3, 4); +#ifdef GDAL_COMPILATION + +const char CPL_DLL *CPLSPrintf(CPL_FORMAT_STRING(const char *fmt), ...) + CPL_PRINT_FUNC_FORMAT(1, 2) CPL_WARN_UNUSED_RESULT; + /** Similar to CPLError(), but only execute it once during the life-time * of a process. * * @since 3.11 */ -#define CPLErrorOnce(...) \ +#define CPLErrorOnce(eErrClass, err_no, ...) \ do \ { \ static bool lbCPLErrorOnce = false; \ if (!lbCPLErrorOnce) \ { \ lbCPLErrorOnce = true; \ - CPLError(__VA_ARGS__); \ + const char *lCPLErrorMsg = CPLSPrintf(__VA_ARGS__); \ + const size_t lCPLErrorMsgLen = strlen(lCPLErrorMsg); \ + const char *lCPLErrorMsgSuffix = \ + " Further messages of this type will be suppressed."; \ + if (lCPLErrorMsgLen && lCPLErrorMsg[lCPLErrorMsgLen - 1] == '.') \ + CPLError((eErrClass), (err_no), "%s%s", lCPLErrorMsg, \ + lCPLErrorMsgSuffix); \ + else \ + CPLError((eErrClass), (err_no), "%s.%s", lCPLErrorMsg, \ + lCPLErrorMsgSuffix); \ } \ } while (0) +#endif void CPL_DLL CPLErrorV(CPLErr, CPLErrorNum, const char *, va_list); void CPL_DLL CPLEmergencyError(const char *) CPL_NO_RETURN; @@ -189,6 +204,7 @@ void CPL_DLL CPL_STDCALL CPLPopErrorHandler(void); { \ } while (0) /* Eat all CPLDebugProgress calls. */ +#ifdef GDAL_COMPILATION /** Similar to CPLDebug(), but only execute it once during the life-time * of a process. * @@ -198,6 +214,7 @@ void CPL_DLL CPL_STDCALL CPLPopErrorHandler(void); do \ { \ } while (0) +#endif #else void CPL_DLL CPLDebug(const char *, CPL_FORMAT_STRING(const char *), ...) @@ -205,21 +222,32 @@ void CPL_DLL CPLDebug(const char *, CPL_FORMAT_STRING(const char *), ...) void CPL_DLL CPLDebugProgress(const char *, CPL_FORMAT_STRING(const char *), ...) CPL_PRINT_FUNC_FORMAT(2, 3); +#ifdef GDAL_COMPILATION /** Similar to CPLDebug(), but only execute it once during the life-time * of a process. * * @since 3.11 */ -#define CPLDebugOnce(...) \ +#define CPLDebugOnce(category, ...) \ do \ { \ - static bool lbCPLErrorOnce = false; \ - if (!lbCPLErrorOnce) \ + static bool lbCPLDebugOnce = false; \ + if (!lbCPLDebugOnce) \ { \ - lbCPLErrorOnce = true; \ - CPLDebug(__VA_ARGS__); \ + lbCPLDebugOnce = true; \ + const char *lCPLDebugMsg = CPLSPrintf(__VA_ARGS__); \ + const size_t lCPLErrorMsgLen = strlen(lCPLDebugMsg); \ + const char *lCPLDebugMsgSuffix = \ + " Further messages of this type will be suppressed."; \ + if (lCPLErrorMsgLen && lCPLDebugMsg[lCPLErrorMsgLen - 1] == '.') \ + CPLDebug((category), "%s%s", lCPLDebugMsg, \ + lCPLDebugMsgSuffix); \ + else \ + CPLDebug((category), "%s.%s", lCPLDebugMsg, \ + lCPLDebugMsgSuffix); \ } \ } while (0) +#endif #endif From 697111ea5b00aabc8f61efef1e94c127eba6a926 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Jan 2025 21:03:48 +0100 Subject: [PATCH 13/14] Doc: vrt.rst: fix pansharpened XML example --- doc/source/drivers/raster/vrt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/drivers/raster/vrt.rst b/doc/source/drivers/raster/vrt.rst index 7183c7929f35..6c7ecc404232 100644 --- a/doc/source/drivers/raster/vrt.rst +++ b/doc/source/drivers/raster/vrt.rst @@ -1919,7 +1919,7 @@ pseudo panchromatic intensity, but not bound to an output band. world_rgbnir.tif 2 - + world_rgbnir.tif 3 From c6bedc3cd47ca133a27af91d95fdef79b8241bfe Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Jan 2025 22:11:36 +0100 Subject: [PATCH 14/14] Python bindings: fix Algorithm.GetAsDatasetValue() memory leak (master only) --- swig/include/Algorithm.i | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swig/include/Algorithm.i b/swig/include/Algorithm.i index 8a07b9412bf8..da869b0c130f 100644 --- a/swig/include/Algorithm.i +++ b/swig/include/Algorithm.i @@ -162,7 +162,7 @@ public: return GDALAlgorithmArgGetAsDouble(self); } -%newobject GetAsDataset; +%newobject GetAsDatasetValue; GDALArgDatasetValueHS* GetAsDatasetValue() { return GDALAlgorithmArgGetAsDatasetValue(self); }