diff --git a/.github/workflows/code_checks.yml b/.github/workflows/code_checks.yml index 86279a9ddc03..2a276e9493d6 100644 --- a/.github/workflows/code_checks.yml +++ b/.github/workflows/code_checks.yml @@ -66,6 +66,40 @@ jobs: cd build ../scripts/cppcheck.sh + cppcheck_master: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Install Requirements + run: | + sudo apt update + sudo apt install -y git libsqlite3-dev ccache sqlite3 libproj-dev cmake g++ make + + - name: Build cppcheck + run: | + git clone https://github.com/danmar/cppcheck + cd cppcheck + mkdir build + cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + make -j$(nproc) + sudo make install + cd ../.. + + - name: Run cmake + run: | + mkdir build + cd build + cmake .. + + - name: Run cppcheck test + run: | + cd build + # Do not fail the job. This is just used as a tool to monitor how we are regarding recent cppcheck + ../scripts/cppcheck.sh || /bin/true + code_quality_checks: runs-on: ubuntu-latest steps: @@ -144,7 +178,8 @@ jobs: - name: Install Requirements run: | sudo apt install python3-pip wget - sudo pip3 install cffconvert + # ruamel.yaml.clib 0.2.9 throws a 'TypeError: a string or stream input is required' when running cffconvert --validate + sudo pip3 install cffconvert "ruamel.yaml.clib<0.2.9" - name: Validate citation file run: | diff --git a/.github/workflows/ubuntu_24.04/Dockerfile.ci b/.github/workflows/ubuntu_24.04/Dockerfile.ci index 9d4f4137480d..c6c830fca30c 100644 --- a/.github/workflows/ubuntu_24.04/Dockerfile.ci +++ b/.github/workflows/ubuntu_24.04/Dockerfile.ci @@ -32,7 +32,6 @@ RUN apt-get update && \ libgif-dev \ libhdf4-alt-dev \ libhdf5-serial-dev \ - libheif-dev \ libjpeg-dev \ libjxl-dev \ libkml-dev \ @@ -74,6 +73,16 @@ RUN apt-get update && \ wget \ zip +# temporary libheif build +RUN apt-get install -y --allow-unauthenticated libaom-dev libbrotli-dev libde265-dev libx265-dev +RUN git clone --depth 1 https://github.com/strukturag/libheif.git libheif-git && \ + cd libheif-git && \ + mkdir build && \ + cd build && \ + cmake --preset=develop .. && \ + make -j$(nproc) && \ + make install + # MSSQL: client side RUN curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - RUN curl https://packages.microsoft.com/config/ubuntu/22.04/prod.list | tee /etc/apt/sources.list.d/msprod.list diff --git a/.github/workflows/ubuntu_24.04/expected_gdalinfo_formats.txt b/.github/workflows/ubuntu_24.04/expected_gdalinfo_formats.txt index 3940ca1d8b77..2be3770e4ce0 100644 --- a/.github/workflows/ubuntu_24.04/expected_gdalinfo_formats.txt +++ b/.github/workflows/ubuntu_24.04/expected_gdalinfo_formats.txt @@ -2,7 +2,7 @@ Supported Formats: (ro:read-only, rw:read-write, +:update, v:virtual-I/O s:subda VRT -raster,multidimensional raster- (rw+v): Virtual Raster (*.vrt) DERIVED -raster- (ro): Derived datasets using VRT pixel functions GTI -raster- (rov): GDAL Raster Tile Index (*.gti.gpkg, *.gti.fgb, *.gti) - SNAP_TIFF -raster- (rov): Sentinel Application Processing GeoTIFF (*.tif, *.tiff) + SNAP_TIFF -raster- (rov): Sentinel Application Processing GeoTIFF GTiff -raster- (rw+vs): GeoTIFF (*.tif, *.tiff) COG -raster- (wv): Cloud optimized GeoTIFF generator (*.tif, *.tiff) NITF -raster- (rw+vs): National Imagery Transmission Format (*.ntf) @@ -137,7 +137,7 @@ Supported Formats: (ro:read-only, rw:read-write, +:update, v:virtual-I/O s:subda SIGDEM -raster- (rwv): Scaled Integer Gridded DEM .sigdem (*.sigdem) EXR -raster- (rw+vs): Extended Dynamic Range Image File Format (*.exr) AVIF -raster- (rwvs): AV1 Image File Format (*.avif) - HEIF -raster- (rov): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic) + HEIF -raster- (rwv): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic) TGA -raster- (rov): TGA/TARGA Image File Format (*.tga) OGCAPI -raster,vector- (rov): OGCAPI STACTA -raster- (rovs): Spatio-Temporal Asset Catalog Tiled Assets (*.json) diff --git a/.github/workflows/windows_conda_expected_gdalinfo_formats.txt b/.github/workflows/windows_conda_expected_gdalinfo_formats.txt index 4c384b47ac5a..44fdb6efd66a 100644 --- a/.github/workflows/windows_conda_expected_gdalinfo_formats.txt +++ b/.github/workflows/windows_conda_expected_gdalinfo_formats.txt @@ -2,7 +2,7 @@ Supported Formats: (ro:read-only, rw:read-write, +:update, v:virtual-I/O s:subda VRT -raster,multidimensional raster- (rw+v): Virtual Raster (*.vrt) DERIVED -raster- (ro): Derived datasets using VRT pixel functions GTI -raster- (rov): GDAL Raster Tile Index (*.gti.gpkg, *.gti.fgb, *.gti) - SNAP_TIFF -raster- (rov): Sentinel Application Processing GeoTIFF (*.tif, *.tiff) + SNAP_TIFF -raster- (rov): Sentinel Application Processing GeoTIFF GTiff -raster- (rw+vs): GeoTIFF (*.tif, *.tiff) COG -raster- (wv): Cloud optimized GeoTIFF generator (*.tif, *.tiff) NITF -raster- (rw+vs): National Imagery Transmission Format (*.ntf) @@ -137,7 +137,7 @@ Supported Formats: (ro:read-only, rw:read-write, +:update, v:virtual-I/O s:subda DAAS -raster- (ro): Airbus DS Intelligence Data As A Service driver SIGDEM -raster- (rwv): Scaled Integer Gridded DEM .sigdem (*.sigdem) AVIF -raster- (rwvs): AV1 Image File Format (*.avif) - HEIF -raster- (rov): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic) + HEIF -raster- (rwv): ISO/IEC 23008-12:2017 High Efficiency Image File Format (*.heic) TGA -raster- (rov): TGA/TARGA Image File Format (*.tga) OGCAPI -raster,vector- (rov): OGCAPI STACTA -raster- (rovs): Spatio-Temporal Asset Catalog Tiled Assets (*.json) diff --git a/MIGRATION_GUIDE.TXT b/MIGRATION_GUIDE.TXT index a9df79f4e342..085f767107d3 100644 --- a/MIGRATION_GUIDE.TXT +++ b/MIGRATION_GUIDE.TXT @@ -223,7 +223,7 @@ MIGRATION GUIDE FROM GDAL 2.4 to GDAL 3.0 - Unix build: Arguments of --with-pg changed to yes/no only. - Substantial changes, sometimes backward incompatible, in coordinate reference system and coordinate transformations have been introduced per - https://trac.osgeo.org/gdal/wiki/rfc73_proj6_wkt2_srsbarn + https://gdal.org/en/latest/development/rfc/rfc73_proj6_wkt2_srsbarn.html * OSRImportFromEPSG() takes into account official axis order. Traditional GIS-friendly axis order can be restored with OGRSpatialReference::SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); @@ -295,7 +295,7 @@ MIGRATION GUIDE FROM GDAL 2.2 to GDAL 2.3 1) RFC 70: Guessing output format from output file name extension for utilities -Link: https://trac.osgeo.org/gdal/wiki/rfc70_output_format_guess +Link: https://gdal.org/en/latest/development/rfc/rfc70_output_format_guess.html Before GDAL 2.3, if not specifying the output format to utilities, GeoTIFF or Shapefile were assumed for most utilities. Now, the output format will be @@ -305,7 +305,7 @@ not specified (but warnings were already emitted in such situations). 2) RFC 68: C++11 Compilation requirement -Link: https://trac.osgeo.org/gdal/wiki/rfc68_cplusplus11 +Link: https://gdal.org/en/latest/development/rfc/rfc68_cplusplus11.html GDAL now requires a C++11 compatible compiler. External code using GDAL C++ API will also need to enable at least C++11 compilation mode, if the compiler @@ -349,7 +349,7 @@ MIGRATION GUIDE FROM GDAL 2.1 to GDAL 2.2 A) RFC 64: Triangle, Polyhedral surface and TIN -Link: https://trac.osgeo.org/gdal/wiki/rfc64_triangle_polyhedralsurface_tin +Link: https://gdal.org/en/latest/development/rfc/rfc64_triangle_polyhedralsurface_tin.html Vector drivers can now return geometries of type wkbPolyhedralSurface, wkbTIN and wkbTriangle; and their Z, M and ZM variants (as well as for the type of @@ -358,7 +358,7 @@ writing geometries, must be ready to deal with them. B) RFC 67: Null values in OGR -Link: https://trac.osgeo.org/gdal/wiki/rfc67_nullfieldvalues +Link: https://gdal.org/en/latest/development/rfc/rfc67_nullfieldvalues.html Previously, the "unset" state of a field was used both for a unset state (ie no information for the field of the feature) or the NULL state of the @@ -383,7 +383,7 @@ MIGRATION GUIDE FROM GDAL 2.0 to GDAL 2.1 A) RFC 61: Support for measured geometries -Link: https://trac.osgeo.org/gdal/wiki/rfc61_support_for_measured_geometries +Link: https://gdal.org/en/latest/development/rfc/rfc61_support_for_measured_geometries.html The OGRwkbGeometryType enumeration has been extended with new values for the M and ZM variants of the geometry types. Client code may have to be upgraded @@ -405,7 +405,7 @@ Changes to the Perl bindings API are listed in swig/perl/Changes-in-the-API-in-2 A) RFC 46: Unification of GDAL and OGR driver models -Link: http://trac.osgeo.org/gdal/wiki/rfc46_gdal_ogr_unification +Link: https://gdal.org/en/latest/development/rfc/rfc46_gdal_ogr_unification.html C++ API: @@ -449,7 +449,7 @@ Behavior changes : B) RFC 49: Curve geometries -Link: http://trac.osgeo.org/gdal/wiki/rfc49_curve_geometries +Link: https://gdal.org/en/latest/development/rfc/rfc49_curve_geometries.html C/C++ API : @@ -470,7 +470,7 @@ Out-of-tree drivers : C) RFC 51: RasterIO() improvements : resampling and progress callback -Link: http://trac.osgeo.org/gdal/wiki/rfc51_rasterio_resampling_progress +Link: https://gdal.org/en/latest/development/rfc/rfc51_rasterio_resampling_progress.html Out-of-tree drivers : @@ -481,7 +481,7 @@ Out-of-tree drivers : D) RFC 31: OGR 64bit Integer Fields and FIDs -Link:http://trac.osgeo.org/gdal/wiki/rfc31_ogr_64 +Link: https://gdal.org/en/latest/development/rfc/rfc31_ogr_64.html C++ API: * OGRLayer::GetFeature(), OGRLayer::DeleteFeature(), OGRLayer::SetNextByIndex() take a GIntBig instead of a long @@ -502,7 +502,7 @@ Out-of-tree drivers : E) RFC 52: Strict OGR SQL quoting -Link: http://trac.osgeo.org/gdal/wiki/rfc52_strict_sql_quoting +Link: https://gdal.org/en/latest/development/rfc/rfc52_strict_sql_quoting.html No API changes @@ -517,7 +517,7 @@ Behavior changes: F) RFC 53: OGR not-null constraints and default values -Link: http://trac.osgeo.org/gdal/wiki/rfc53_ogr_notnull_default +Link: https://gdal.org/en/latest/development/rfc/rfc53_ogr_notnull_default.html API changes: * OGRFieldDefn::SetDefault() now takes a const char* as argument. @@ -526,7 +526,7 @@ API changes: G) RFC 54: Dataset transactions -Link: http://trac.osgeo.org/gdal/wiki/rfc54_dataset_transactions +Link: https://gdal.org/en/latest/development/rfc/rfc54_dataset_transactions.html Only API additions. @@ -542,7 +542,7 @@ Behavior changes: H) RFC 55: Refined SetFeature() and DeleteFeature() semantics -Link: http://trac.osgeo.org/gdal/wiki/rfc55_refined_setfeature_deletefeature_semantics +Link: https://gdal.org/en/latest/development/rfc/rfc55_refined_setfeature_deletefeature_semantics.html Behavior changes: * Drivers will now return OGRERR_NON_EXISTING_FEATURE when calling SetFeature() @@ -550,7 +550,7 @@ Behavior changes: I) RFC 56: -Link: https://trac.osgeo.org/gdal/wiki/rfc56_millisecond_precision +Link: https://gdal.org/en/latest/development/rfc/rfc56_millisecond_precision.html API/ABI changes: @@ -593,7 +593,7 @@ Behavior changes: J) RFC 57: 64-bit bucket count for histograms -Link: https://trac.osgeo.org/gdal/wiki/rfc57_histogram_64bit_count +Link: https://gdal.org/en/latest/development/rfc/rfc57_histogram_64bit_count.html C++ API: * GDALRasterBand::GetHistogram() and GDALRasterBand::SetDefaultHistogram() take a GUIntBig* instead of a int* for bucket counts. @@ -616,7 +616,7 @@ This file documents backwards incompatible changes. C++ API: * GDALRasterAttributeTable is now an abstract class. - See http://trac.osgeo.org/gdal/wiki/rfc40_enhanced_rat_support + See https://gdal.org/en/latest/development/rfc/rfc40_enhanced_rat_support.html The functionality of GDAL 1.X GDALRasterAttributeTable is now in GDALDefaultRasterAttributeTable. @@ -635,4 +635,4 @@ Changes that should likely not impact anybody : * OGRGeometryFactory::getGEOSGeometryFactory() has been removed. This method returned NULL since 2006 - ( http://trac.osgeo.org/gdal/changeset/9899/trunk/ogr/ogrgeometryfactory.cpp ) + ( https://github.com/OSGeo/gdal/commit/42d5fd976795b9c85aac2c4ffac12025e21697c1#diff-9b267dec2a69d6f56247a5525195973890780ce50ae8c9c809bf4818754f4e46L885 ) diff --git a/alg/gdalwarpkernel.cpp b/alg/gdalwarpkernel.cpp index 565a6657a189..52c58d0d219b 100644 --- a/alg/gdalwarpkernel.cpp +++ b/alg/gdalwarpkernel.cpp @@ -3309,7 +3309,13 @@ static double GWKLanczosSinc(double dfX) const double dfPIX = M_PI * dfX; const double dfPIXoverR = dfPIX / 3; const double dfPIX2overR = dfPIX * dfPIXoverR; - return sin(dfPIX) * sin(dfPIXoverR) / dfPIX2overR; + // Given that sin(3x) = 3 sin(x) - 4 sin^3 (x) + // we can compute sin(dfSinPIX) from sin(dfPIXoverR) + const double dfSinPIXoverR = sin(dfPIXoverR); + const double dfSinPIXoverRSquared = dfSinPIXoverR * dfSinPIXoverR; + const double dfSinPIXMulSinPIXoverR = + (3 - 4 * dfSinPIXoverRSquared) * dfSinPIXoverRSquared; + return dfSinPIXMulSinPIXoverR / dfPIX2overR; } static double GWKLanczosSinc4Values(double *padfValues) @@ -3325,7 +3331,13 @@ static double GWKLanczosSinc4Values(double *padfValues) const double dfPIX = M_PI * padfValues[i]; const double dfPIXoverR = dfPIX / 3; const double dfPIX2overR = dfPIX * dfPIXoverR; - padfValues[i] = sin(dfPIX) * sin(dfPIXoverR) / dfPIX2overR; + // Given that sin(3x) = 3 sin(x) - 4 sin^3 (x) + // we can compute sin(dfSinPIX) from sin(dfPIXoverR) + const double dfSinPIXoverR = sin(dfPIXoverR); + const double dfSinPIXoverRSquared = dfSinPIXoverR * dfSinPIXoverR; + const double dfSinPIXMulSinPIXoverR = + (3 - 4 * dfSinPIXoverRSquared) * dfSinPIXoverRSquared; + padfValues[i] = dfSinPIXMulSinPIXoverR / dfPIX2overR; } } return padfValues[0] + padfValues[1] + padfValues[2] + padfValues[3]; @@ -3502,11 +3514,19 @@ struct _GWKResampleWrkStruct double *padfWeightsX; bool *pabCalcX; - double *padfWeightsY; // Only used by GWKResampleOptimizedLanczos. - int iLastSrcX; // Only used by GWKResampleOptimizedLanczos. - int iLastSrcY; // Only used by GWKResampleOptimizedLanczos. - double dfLastDeltaX; // Only used by GWKResampleOptimizedLanczos. - double dfLastDeltaY; // Only used by GWKResampleOptimizedLanczos. + double *padfWeightsY; // Only used by GWKResampleOptimizedLanczos. + int iLastSrcX; // Only used by GWKResampleOptimizedLanczos. + int iLastSrcY; // Only used by GWKResampleOptimizedLanczos. + double dfLastDeltaX; // Only used by GWKResampleOptimizedLanczos. + double dfLastDeltaY; // Only used by GWKResampleOptimizedLanczos. + double dfCosPiXScale; // Only used by GWKResampleOptimizedLanczos. + double dfSinPiXScale; // Only used by GWKResampleOptimizedLanczos. + double dfCosPiXScaleOver3; // Only used by GWKResampleOptimizedLanczos. + double dfSinPiXScaleOver3; // Only used by GWKResampleOptimizedLanczos. + double dfCosPiYScale; // Only used by GWKResampleOptimizedLanczos. + double dfSinPiYScale; // Only used by GWKResampleOptimizedLanczos. + double dfCosPiYScaleOver3; // Only used by GWKResampleOptimizedLanczos. + double dfSinPiYScaleOver3; // Only used by GWKResampleOptimizedLanczos. // Space for saving a row of pixels. double *padfRowDensity; @@ -3534,7 +3554,7 @@ static GWKResampleWrkStruct *GWKResampleCreateWrkStruct(GDALWarpKernel *poWK) const int nYDist = (poWK->nYRadius + 1) * 2; GWKResampleWrkStruct *psWrkStruct = static_cast( - CPLMalloc(sizeof(GWKResampleWrkStruct))); + CPLCalloc(1, sizeof(GWKResampleWrkStruct))); // Alloc space for saved X weights. psWrkStruct->padfWeightsX = @@ -3570,38 +3590,40 @@ static GWKResampleWrkStruct *GWKResampleCreateWrkStruct(GDALWarpKernel *poWK) { psWrkStruct->pfnGWKResample = GWKResampleOptimizedLanczos; - const double dfXScale = poWK->dfXScale; - if (dfXScale < 1.0) + if (poWK->dfXScale < 1) { - int iMin = poWK->nFiltInitX; - int iMax = poWK->nXRadius; - while (iMin * dfXScale < -3.0) - iMin++; - while (iMax * dfXScale > 3.0) - iMax--; - - for (int i = iMin; i <= iMax; ++i) - { - psWrkStruct->padfWeightsX[i - poWK->nFiltInitX] = - GWKLanczosSinc(i * dfXScale); - } + psWrkStruct->dfCosPiXScaleOver3 = cos(M_PI / 3 * poWK->dfXScale); + psWrkStruct->dfSinPiXScaleOver3 = + sqrt(1 - psWrkStruct->dfCosPiXScaleOver3 * + psWrkStruct->dfCosPiXScaleOver3); + // "Naive": + // const double dfCosPiXScale = cos( M_PI * dfXScale ); + // const double dfSinPiXScale = sin( M_PI * dfXScale ); + // but given that cos(3x) = 4 cos^3(x) - 3 cos(x) and x between 0 and M_PI + psWrkStruct->dfCosPiXScale = (4 * psWrkStruct->dfCosPiXScaleOver3 * + psWrkStruct->dfCosPiXScaleOver3 - + 3) * + psWrkStruct->dfCosPiXScaleOver3; + psWrkStruct->dfSinPiXScale = sqrt( + 1 - psWrkStruct->dfCosPiXScale * psWrkStruct->dfCosPiXScale); } - const double dfYScale = poWK->dfYScale; - if (dfYScale < 1.0) + if (poWK->dfYScale < 1) { - int jMin = poWK->nFiltInitY; - int jMax = poWK->nYRadius; - while (jMin * dfYScale < -3.0) - jMin++; - while (jMax * dfYScale > 3.0) - jMax--; - - for (int j = jMin; j <= jMax; ++j) - { - psWrkStruct->padfWeightsY[j - poWK->nFiltInitY] = - GWKLanczosSinc(j * dfYScale); - } + psWrkStruct->dfCosPiYScaleOver3 = cos(M_PI / 3 * poWK->dfYScale); + psWrkStruct->dfSinPiYScaleOver3 = + sqrt(1 - psWrkStruct->dfCosPiYScaleOver3 * + psWrkStruct->dfCosPiYScaleOver3); + // "Naive": + // const double dfCosPiYScale = cos( M_PI * dfYScale ); + // const double dfSinPiYScale = sin( M_PI * dfYScale ); + // but given that cos(3x) = 4 cos^3(x) - 3 cos(x) and x between 0 and M_PI + psWrkStruct->dfCosPiYScale = (4 * psWrkStruct->dfCosPiYScaleOver3 * + psWrkStruct->dfCosPiYScaleOver3 - + 3) * + psWrkStruct->dfCosPiYScaleOver3; + psWrkStruct->dfSinPiYScale = sqrt( + 1 - psWrkStruct->dfCosPiYScale * psWrkStruct->dfCosPiYScale); } } else @@ -3816,13 +3838,15 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, const double dfYScale = poWK->dfYScale; // Space for saved X weights. - double *padfWeightsX = psWrkStruct->padfWeightsX; - double *padfWeightsY = psWrkStruct->padfWeightsY; + double *const padfWeightsXShifted = + psWrkStruct->padfWeightsX - poWK->nFiltInitX; + double *const padfWeightsYShifted = + psWrkStruct->padfWeightsY - poWK->nFiltInitY; // Space for saving a row of pixels. - double *padfRowDensity = psWrkStruct->padfRowDensity; - double *padfRowReal = psWrkStruct->padfRowReal; - double *padfRowImag = psWrkStruct->padfRowImag; + double *const padfRowDensity = psWrkStruct->padfRowDensity; + double *const padfRowReal = psWrkStruct->padfRowReal; + double *const padfRowImag = psWrkStruct->padfRowImag; // Skip sampling over edge of image. int jMin = poWK->nFiltInitY; @@ -3841,11 +3865,86 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, if (dfXScale < 1.0) { - while (iMin * dfXScale < -3.0) + while ((iMin - dfDeltaX) * dfXScale < -3.0) iMin++; - while (iMax * dfXScale > 3.0) + while ((iMax - dfDeltaX) * dfXScale > 3.0) iMax--; - // padfWeightsX computed in GWKResampleCreateWrkStruct. + + // clang-format off + /* + Naive version: + for (int i = iMin; i <= iMax; ++i) + { + psWrkStruct->padfWeightsXShifted[i] = + GWKLanczosSinc((i - dfDeltaX) * dfXScale); + } + + but given that: + + GWKLanczosSinc(x): + if (dfX == 0.0) + return 1.0; + + const double dfPIX = M_PI * dfX; + const double dfPIXoverR = dfPIX / 3; + const double dfPIX2overR = dfPIX * dfPIXoverR; + return sin(dfPIX) * sin(dfPIXoverR) / dfPIX2overR; + + and + sin (a + b) = sin a cos b + cos a sin b. + cos (a + b) = cos a cos b - sin a sin b. + + we can skip any sin() computation within the loop + */ + // clang-format on + + if (iSrcX != psWrkStruct->iLastSrcX || + dfDeltaX != psWrkStruct->dfLastDeltaX) + { + double dfX = (iMin - dfDeltaX) * dfXScale; + + double dfPIXover3 = M_PI / 3 * dfX; + double dfCosOver3 = cos(dfPIXover3); + double dfSinOver3 = sin(dfPIXover3); + + // "Naive": + // double dfSin = sin( M_PI * dfX ); + // double dfCos = cos( M_PI * dfX ); + // but given that cos(3x) = 4 cos^3(x) - 3 cos(x) and sin(3x) = 3 sin(x) - 4 sin^3 (x). + double dfSin = (3 - 4 * dfSinOver3 * dfSinOver3) * dfSinOver3; + double dfCos = (4 * dfCosOver3 * dfCosOver3 - 3) * dfCosOver3; + + const double dfCosPiXScaleOver3 = psWrkStruct->dfCosPiXScaleOver3; + const double dfSinPiXScaleOver3 = psWrkStruct->dfSinPiXScaleOver3; + const double dfCosPiXScale = psWrkStruct->dfCosPiXScale; + const double dfSinPiXScale = psWrkStruct->dfSinPiXScale; + constexpr double THREE_PI_PI = 3 * M_PI * M_PI; + padfWeightsXShifted[iMin] = + dfX == 0 ? 1.0 : THREE_PI_PI * dfSin * dfSinOver3 / (dfX * dfX); + for (int i = iMin + 1; i <= iMax; ++i) + { + dfX += dfXScale; + const double dfNewSin = + dfSin * dfCosPiXScale + dfCos * dfSinPiXScale; + const double dfNewSinOver3 = dfSinOver3 * dfCosPiXScaleOver3 + + dfCosOver3 * dfSinPiXScaleOver3; + padfWeightsXShifted[i] = + dfX == 0 + ? 1.0 + : THREE_PI_PI * dfNewSin * dfNewSinOver3 / (dfX * dfX); + const double dfNewCos = + dfCos * dfCosPiXScale - dfSin * dfSinPiXScale; + const double dfNewCosOver3 = dfCosOver3 * dfCosPiXScaleOver3 - + dfSinOver3 * dfSinPiXScaleOver3; + dfSin = dfNewSin; + dfCos = dfNewCos; + dfSinOver3 = dfNewSinOver3; + dfCosOver3 = dfNewCosOver3; + } + + psWrkStruct->iLastSrcX = iSrcX; + psWrkStruct->dfLastDeltaX = dfDeltaX; + } } else { @@ -3861,17 +3960,18 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, // following trigonometric formulas. // TODO(schwehr): Move this somewhere where it can be rendered at - // LaTeX. sin(M_PI * (dfBase + k)) = sin(M_PI * dfBase) * cos(M_PI * - // k) + cos(M_PI * dfBase) * sin(M_PI * k) sin(M_PI * (dfBase + k)) - // = dfSinPIBase * cos(M_PI * k) + dfCosPIBase * sin(M_PI * k) + // LaTeX. + // clang-format off + // sin(M_PI * (dfBase + k)) = sin(M_PI * dfBase) * cos(M_PI * k) + + // cos(M_PI * dfBase) * sin(M_PI * k) + // sin(M_PI * (dfBase + k)) = dfSinPIBase * cos(M_PI * k) + dfCosPIBase * sin(M_PI * k) // sin(M_PI * (dfBase + k)) = dfSinPIBase * cos(M_PI * k) - // sin(M_PI * (dfBase + k)) = dfSinPIBase * (((k % 2) == 0) ? 1 : - // -1) + // sin(M_PI * (dfBase + k)) = dfSinPIBase * (((k % 2) == 0) ? 1 : -1) - // sin(M_PI / dfR * (dfBase + k)) = sin(M_PI / dfR * dfBase) * - // cos(M_PI / dfR * k) + cos(M_PI / dfR * dfBase) * sin(M_PI / dfR * - // k) sin(M_PI / dfR * (dfBase + k)) = dfSinPIBaseOverR * cos(M_PI / - // dfR * k) + dfCosPIBaseOverR * sin(M_PI / dfR * k) + // sin(M_PI / dfR * (dfBase + k)) = sin(M_PI / dfR * dfBase) * cos(M_PI / dfR * k) + + // cos(M_PI / dfR * dfBase) * sin(M_PI / dfR * k) + // sin(M_PI / dfR * (dfBase + k)) = dfSinPIBaseOverR * cos(M_PI / dfR * k) + dfCosPIBaseOverR * sin(M_PI / dfR * k) + // clang-format on const double dfSinPIDeltaXOver3 = sin((-M_PI / 3.0) * dfDeltaX); const double dfSin2PIDeltaXOver3 = @@ -3899,10 +3999,9 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, { const double dfX = i - dfDeltaX; if (dfX == 0.0) - padfWeightsX[i - poWK->nFiltInitX] = 1.0; + padfWeightsXShifted[i] = 1.0; else - padfWeightsX[i - poWK->nFiltInitX] = - padfCst[(i + 3) % 3] / (dfX * dfX); + padfWeightsXShifted[i] = padfCst[(i + 3) % 3] / (dfX * dfX); #if DEBUG_VERBOSE // TODO(schwehr): AlmostEqual. // CPLAssert(fabs(padfWeightsX[i-poWK->nFiltInitX] - @@ -3917,11 +4016,69 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, if (dfYScale < 1.0) { - while (jMin * dfYScale < -3.0) + while ((jMin - dfDeltaY) * dfYScale < -3.0) jMin++; - while (jMax * dfYScale > 3.0) + while ((jMax - dfDeltaY) * dfYScale > 3.0) jMax--; - // padfWeightsY computed in GWKResampleCreateWrkStruct. + + // clang-format off + /* + Naive version: + for (int j = jMin; j <= jMax; ++j) + { + padfWeightsYShifted[j] = + GWKLanczosSinc((j - dfDeltaY) * dfYScale); + } + */ + // clang-format on + + if (iSrcY != psWrkStruct->iLastSrcY || + dfDeltaY != psWrkStruct->dfLastDeltaY) + { + double dfY = (jMin - dfDeltaY) * dfYScale; + + double dfPIYover3 = M_PI / 3 * dfY; + double dfCosOver3 = cos(dfPIYover3); + double dfSinOver3 = sin(dfPIYover3); + + // "Naive": + // double dfSin = sin( M_PI * dfY ); + // double dfCos = cos( M_PI * dfY ); + // but given that cos(3x) = 4 cos^3(x) - 3 cos(x) and sin(3x) = 3 sin(x) - 4 sin^3 (x). + double dfSin = (3 - 4 * dfSinOver3 * dfSinOver3) * dfSinOver3; + double dfCos = (4 * dfCosOver3 * dfCosOver3 - 3) * dfCosOver3; + + const double dfCosPiYScaleOver3 = psWrkStruct->dfCosPiYScaleOver3; + const double dfSinPiYScaleOver3 = psWrkStruct->dfSinPiYScaleOver3; + const double dfCosPiYScale = psWrkStruct->dfCosPiYScale; + const double dfSinPiYScale = psWrkStruct->dfSinPiYScale; + constexpr double THREE_PI_PI = 3 * M_PI * M_PI; + padfWeightsYShifted[jMin] = + dfY == 0 ? 1.0 : THREE_PI_PI * dfSin * dfSinOver3 / (dfY * dfY); + for (int j = jMin + 1; j <= iMax; ++j) + { + dfY += dfYScale; + const double dfNewSin = + dfSin * dfCosPiYScale + dfCos * dfSinPiYScale; + const double dfNewSinOver3 = dfSinOver3 * dfCosPiYScaleOver3 + + dfCosOver3 * dfSinPiYScaleOver3; + padfWeightsYShifted[j] = + dfY == 0 + ? 1.0 + : THREE_PI_PI * dfNewSin * dfNewSinOver3 / (dfY * dfY); + const double dfNewCos = + dfCos * dfCosPiYScale - dfSin * dfSinPiYScale; + const double dfNewCosOver3 = dfCosOver3 * dfCosPiYScaleOver3 - + dfSinOver3 * dfSinPiYScaleOver3; + dfSin = dfNewSin; + dfCos = dfNewCos; + dfSinOver3 = dfNewSinOver3; + dfCosOver3 = dfNewCosOver3; + } + + psWrkStruct->iLastSrcY = iSrcY; + psWrkStruct->dfLastDeltaY = dfDeltaY; + } } else { @@ -3959,13 +4116,12 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, { const double dfY = j - dfDeltaY; if (dfY == 0.0) - padfWeightsY[j - poWK->nFiltInitY] = 1.0; + padfWeightsYShifted[j] = 1.0; else - padfWeightsY[j - poWK->nFiltInitY] = - padfCst[(j + 3) % 3] / (dfY * dfY); + padfWeightsYShifted[j] = padfCst[(j + 3) % 3] / (dfY * dfY); #if DEBUG_VERBOSE // TODO(schwehr): AlmostEqual. - // CPLAssert(fabs(padfWeightsY[j-poWK->nFiltInitY] - + // CPLAssert(fabs(padfWeightsYShifted[j] - // GWKLanczosSinc(dfY, 3.0)) < 1e-10); #endif } @@ -3975,9 +4131,6 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, } } - GPtrDiff_t iRowOffset = - iSrcOffset + static_cast(jMin - 1) * nSrcXSize + iMin; - // If we have no density information, we can simply compute the // accumulated weight. if (padfRowDensity == nullptr) @@ -3985,20 +4138,128 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, double dfRowAccWeight = 0.0; for (int i = iMin; i <= iMax; ++i) { - dfRowAccWeight += padfWeightsX[i - poWK->nFiltInitX]; + dfRowAccWeight += padfWeightsXShifted[i]; } double dfColAccWeight = 0.0; for (int j = jMin; j <= jMax; ++j) { - dfColAccWeight += padfWeightsY[j - poWK->nFiltInitY]; + dfColAccWeight += padfWeightsYShifted[j]; } dfAccumulatorWeight = dfRowAccWeight * dfColAccWeight; } - const bool bIsNonComplex = !GDALDataTypeIsComplex(poWK->eWorkingDataType); - // Loop over pixel rows in the kernel. + + if (poWK->eWorkingDataType == GDT_Byte && !poWK->panUnifiedSrcValid && + !poWK->papanBandSrcValid && !poWK->pafUnifiedSrcDensity && + !padfRowDensity) + { + // Optimization for Byte case without any masking/alpha + + if (dfAccumulatorWeight < 0.000001) + { + *pdfDensity = 0.0; + return false; + } + + const GByte *pSrc = + reinterpret_cast(poWK->papabySrcImage[iBand]); + pSrc += iSrcOffset + static_cast(jMin) * nSrcXSize; + +#if defined(__x86_64) || defined(_M_X64) + if (iMax - iMin + 1 == 6) + { + // This is just an optimized version of the general case in + // the else clause. + + pSrc += iMin; + int j = jMin; + const auto fourXWeights = + XMMReg4Double::Load4Val(padfWeightsXShifted + iMin); + + // Process 2 lines at the same time. + for (; j < jMax; j += 2) + { + const XMMReg4Double v_acc = + XMMReg4Double::Load4Val(pSrc) * fourXWeights; + const XMMReg4Double v_acc2 = + XMMReg4Double::Load4Val(pSrc + nSrcXSize) * fourXWeights; + const double dfRowAcc = v_acc.GetHorizSum(); + const double dfRowAccEnd = + pSrc[4] * padfWeightsXShifted[iMin + 4] + + pSrc[5] * padfWeightsXShifted[iMin + 5]; + dfAccumulatorReal += + (dfRowAcc + dfRowAccEnd) * padfWeightsYShifted[j]; + const double dfRowAcc2 = v_acc2.GetHorizSum(); + const double dfRowAcc2End = + pSrc[nSrcXSize + 4] * padfWeightsXShifted[iMin + 4] + + pSrc[nSrcXSize + 5] * padfWeightsXShifted[iMin + 5]; + dfAccumulatorReal += + (dfRowAcc2 + dfRowAcc2End) * padfWeightsYShifted[j + 1]; + pSrc += 2 * nSrcXSize; + } + if (j == jMax) + { + // Process last line if there's an odd number of them. + + const XMMReg4Double v_acc = + XMMReg4Double::Load4Val(pSrc) * fourXWeights; + const double dfRowAcc = v_acc.GetHorizSum(); + const double dfRowAccEnd = + pSrc[4] * padfWeightsXShifted[iMin + 4] + + pSrc[5] * padfWeightsXShifted[iMin + 5]; + dfAccumulatorReal += + (dfRowAcc + dfRowAccEnd) * padfWeightsYShifted[j]; + } + } + else +#endif + { + for (int j = jMin; j <= jMax; ++j) + { + int i = iMin; + double dfRowAcc1 = 0.0; + double dfRowAcc2 = 0.0; + // A bit of loop unrolling + for (; i < iMax; i += 2) + { + dfRowAcc1 += pSrc[i] * padfWeightsXShifted[i]; + dfRowAcc2 += pSrc[i + 1] * padfWeightsXShifted[i + 1]; + } + if (i == iMax) + { + // Process last column if there's an odd number of them. + dfRowAcc1 += pSrc[i] * padfWeightsXShifted[i]; + } + + dfAccumulatorReal += + (dfRowAcc1 + dfRowAcc2) * padfWeightsYShifted[j]; + pSrc += nSrcXSize; + } + } + + // Calculate the output taking into account weighting. + if (dfAccumulatorWeight < 0.99999 || dfAccumulatorWeight > 1.00001) + { + const double dfInvAcc = 1.0 / dfAccumulatorWeight; + *pdfReal = dfAccumulatorReal * dfInvAcc; + *pdfDensity = 1.0; + } + else + { + *pdfReal = dfAccumulatorReal; + *pdfDensity = 1.0; + } + + return true; + } + + GPtrDiff_t iRowOffset = + iSrcOffset + static_cast(jMin - 1) * nSrcXSize + iMin; + int nCountValid = 0; + const bool bIsNonComplex = !GDALDataTypeIsComplex(poWK->eWorkingDataType); + for (int j = jMin; j <= jMax; ++j) { iRowOffset += nSrcXSize; @@ -4012,7 +4273,7 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, padfRowDensity, padfRowReal, padfRowImag)) continue; - const double dfWeight1 = padfWeightsY[j - poWK->nFiltInitY]; + const double dfWeight1 = padfWeightsYShifted[j]; // Iterate over pixels in row. if (padfRowDensity != nullptr) @@ -4026,8 +4287,7 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, nCountValid++; // Use a cached set of weights for this row. - const double dfWeight2 = - dfWeight1 * padfWeightsX[i - poWK->nFiltInitX]; + const double dfWeight2 = dfWeight1 * padfWeightsXShifted[i]; // Accumulate! dfAccumulatorReal += padfRowReal[i - iMin] * dfWeight2; @@ -4041,7 +4301,7 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, double dfRowAccReal = 0.0; for (int i = iMin; i <= iMax; ++i) { - const double dfWeight2 = padfWeightsX[i - poWK->nFiltInitX]; + const double dfWeight2 = padfWeightsXShifted[i]; // Accumulate! dfRowAccReal += padfRowReal[i - iMin] * dfWeight2; @@ -4055,7 +4315,7 @@ static bool GWKResampleOptimizedLanczos(const GDALWarpKernel *poWK, int iBand, double dfRowAccImag = 0.0; for (int i = iMin; i <= iMax; ++i) { - const double dfWeight2 = padfWeightsX[i - poWK->nFiltInitX]; + const double dfWeight2 = padfWeightsXShifted[i]; // Accumulate! dfRowAccReal += padfRowReal[i - iMin] * dfWeight2; diff --git a/apps/gdal_translate_lib.cpp b/apps/gdal_translate_lib.cpp index eabc0905355e..bbd42406557c 100644 --- a/apps/gdal_translate_lib.cpp +++ b/apps/gdal_translate_lib.cpp @@ -3276,6 +3276,12 @@ GDALTranslateOptionsNew(char **papszArgv, psOptions->asScaleParams[nIndex].bHaveScaleSrc = false; if (i < argc - 2 && ArgIsNumeric(papszArgv[i + 1])) { + if (!ArgIsNumeric(papszArgv[i + 2])) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Value of -scale must be numeric"); + return nullptr; + } psOptions->asScaleParams[nIndex].bHaveScaleSrc = true; psOptions->asScaleParams[nIndex].dfScaleSrcMin = CPLAtofM(papszArgv[i + 1]); @@ -3287,6 +3293,12 @@ GDALTranslateOptionsNew(char **papszArgv, psOptions->asScaleParams[nIndex].bHaveScaleSrc && ArgIsNumeric(papszArgv[i + 1])) { + if (!ArgIsNumeric(papszArgv[i + 2])) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Value of -scale must be numeric"); + return nullptr; + } psOptions->asScaleParams[nIndex].dfScaleDstMin = CPLAtofM(papszArgv[i + 1]); psOptions->asScaleParams[nIndex].dfScaleDstMax = diff --git a/apps/gdalbuildvrt_lib.cpp b/apps/gdalbuildvrt_lib.cpp index 2d64f222394d..4d7da0545b0a 100644 --- a/apps/gdalbuildvrt_lib.cpp +++ b/apps/gdalbuildvrt_lib.cpp @@ -238,6 +238,7 @@ class VRTBuilder bool bUseSrcMaskBand = true; bool bNoDataFromMask = false; double dfMaskValueThreshold = 0; + CPLStringList aosCreateOptions{}; /* Internal variables */ char *pszProjectionRef = nullptr; @@ -277,7 +278,8 @@ class VRTBuilder const char *pszVRTNoData, bool bUseSrcMaskBand, bool bNoDataFromMask, double dfMaskValueThreshold, const char *pszOutputSRS, const char *pszResampling, - const char *const *papszOpenOptionsIn); + const char *const *papszOpenOptionsIn, + const CPLStringList &aosCreateOptionsIn); ~VRTBuilder(); @@ -299,8 +301,9 @@ VRTBuilder::VRTBuilder( const char *pszSrcNoDataIn, const char *pszVRTNoDataIn, bool bUseSrcMaskBandIn, bool bNoDataFromMaskIn, double dfMaskValueThresholdIn, const char *pszOutputSRSIn, - const char *pszResamplingIn, const char *const *papszOpenOptionsIn) - : bStrict(bStrictIn) + const char *pszResamplingIn, const char *const *papszOpenOptionsIn, + const CPLStringList &aosCreateOptionsIn) + : bStrict(bStrictIn), aosCreateOptions(aosCreateOptionsIn) { pszOutputFilename = CPLStrdup(pszOutputFilenameIn); nInputFiles = nInputFilesIn; @@ -1609,8 +1612,13 @@ GDALDataset *VRTBuilder::Build(GDALProgressFunc pfnProgress, return nullptr; } - VRTDatasetH hVRTDS = VRTCreate(nRasterXSize, nRasterYSize); - GDALSetDescription(hVRTDS, pszOutputFilename); + VRTDatasetH hVRTDS = cpl::down_cast( + VRTDataset::Create(pszOutputFilename, nRasterXSize, nRasterYSize, 0, + GDT_Unknown, aosCreateOptions.List())); + if (!hVRTDS) + { + return nullptr; + } if (pszOutputSRS) { @@ -1742,6 +1750,7 @@ struct GDALBuildVRTOptions std::vector anSelectedBandList{}; std::string osResampling{}; CPLStringList aosOpenOptions{}; + CPLStringList aosCreateOptions{}; bool bUseSrcMaskBand = true; bool bNoDataFromMask = false; double dfMaskValueThreshold = 0; @@ -1892,7 +1901,7 @@ GDALDatasetH GDALBuildVRT(const char *pszDest, int nSrcCount, sOptions.dfMaskValueThreshold, sOptions.osOutputSRS.empty() ? nullptr : sOptions.osOutputSRS.c_str(), sOptions.osResampling.empty() ? nullptr : sOptions.osResampling.c_str(), - sOptions.aosOpenOptions.List()); + sOptions.aosOpenOptions.List(), sOptions.aosCreateOptions); return GDALDataset::ToHandle( oBuilder.Build(sOptions.pfnProgress, sOptions.pProgressData)); @@ -2142,6 +2151,8 @@ GDALBuildVRTOptionsGetParser(GDALBuildVRTOptions *psOptions, argParser->add_open_options_argument(&psOptions->aosOpenOptions); + argParser->add_creation_options_argument(psOptions->aosCreateOptions); + argParser->add_argument("-ignore_srcmaskband") .flag() .action([psOptions](const std::string &) diff --git a/apps/gdalwarp_lib.cpp b/apps/gdalwarp_lib.cpp index 466bda679e9c..093e5aae7183 100644 --- a/apps/gdalwarp_lib.cpp +++ b/apps/gdalwarp_lib.cpp @@ -5753,10 +5753,10 @@ GDALWarpAppOptionsGetParser(GDALWarpAppOptions *psOptions, argParser->add_argument("-et") .metavar("") + .store_into(psOptions->dfErrorThreshold) .action( - [psOptions](const std::string &s) + [psOptions](const std::string &) { - psOptions->dfErrorThreshold = CPLAtofM(s.c_str()); psOptions->aosWarpOptions.AddString(CPLSPrintf( "ERROR_THRESHOLD=%.16g", psOptions->dfErrorThreshold)); }) diff --git a/apps/ogr2ogr_lib.cpp b/apps/ogr2ogr_lib.cpp index e5ef6bf92628..97e1568f8218 100644 --- a/apps/ogr2ogr_lib.cpp +++ b/apps/ogr2ogr_lib.cpp @@ -6177,9 +6177,9 @@ bool LayerTranslator::Translate( int nIters = 1; std::unique_ptr poCollToExplode; int iGeomCollToExplode = -1; + OGRGeometry *poSrcGeometry = nullptr; if (bExplodeCollections) { - OGRGeometry *poSrcGeometry; if (iRequestedSrcGeomField >= 0) poSrcGeometry = poFeature->GetGeomFieldRef(iRequestedSrcGeomField); @@ -6191,7 +6191,9 @@ bool LayerTranslator::Translate( { const int nParts = poSrcGeometry->toGeometryCollection()->getNumGeometries(); - if (nParts > 0) + if (nParts > 0 || + wkbFlatten(poSrcGeometry->getGeometryType()) != + wkbGeometryCollection) { iGeomCollToExplode = iRequestedSrcGeomField >= 0 ? iRequestedSrcGeomField @@ -6199,7 +6201,7 @@ bool LayerTranslator::Translate( poCollToExplode.reset( poFeature->StealGeometry(iGeomCollToExplode) ->toGeometryCollection()); - nIters = nParts; + nIters = std::max(1, nParts); } } } @@ -6416,9 +6418,46 @@ bool LayerTranslator::Translate( if (poCollToExplode && iGeom == iGeomCollToExplode) { - OGRGeometry *poPart = poCollToExplode->getGeometryRef(0); - poCollToExplode->removeGeometry(0, FALSE); - poDstGeometry.reset(poPart); + if (poSrcGeometry && poCollToExplode->IsEmpty()) + { + const OGRwkbGeometryType eSrcType = + poSrcGeometry->getGeometryType(); + const OGRwkbGeometryType eSrcFlattenType = + wkbFlatten(eSrcType); + OGRwkbGeometryType eDstType = eSrcType; + switch (eSrcFlattenType) + { + case wkbMultiPoint: + eDstType = wkbPoint; + break; + case wkbMultiLineString: + eDstType = wkbLineString; + break; + case wkbMultiPolygon: + eDstType = wkbPolygon; + break; + case wkbMultiCurve: + eDstType = wkbCompoundCurve; + break; + case wkbMultiSurface: + eDstType = wkbCurvePolygon; + break; + default: + break; + } + eDstType = + OGR_GT_SetModifier(eDstType, OGR_GT_HasZ(eSrcType), + OGR_GT_HasM(eSrcType)); + poDstGeometry.reset( + OGRGeometryFactory::createGeometry(eDstType)); + } + else + { + OGRGeometry *poPart = + poCollToExplode->getGeometryRef(0); + poCollToExplode->removeGeometry(0, FALSE); + poDstGeometry.reset(poPart); + } } else { @@ -7522,22 +7561,16 @@ static std::unique_ptr GDALVectorTranslateOptionsGetParser( argParser->add_argument("-segmentize") .metavar("") - .action( - [psOptions](const std::string &s) - { - psOptions->eGeomOp = GEOMOP_SEGMENTIZE; - psOptions->dfGeomOpParam = CPLAtofM(s.c_str()); - }) + .store_into(psOptions->dfGeomOpParam) + .action([psOptions](const std::string &) + { psOptions->eGeomOp = GEOMOP_SEGMENTIZE; }) .help(_("Maximum distance between 2 nodes.")); argParser->add_argument("-simplify") .metavar("") - .action( - [psOptions](const std::string &s) - { - psOptions->eGeomOp = GEOMOP_SIMPLIFY_PRESERVE_TOPOLOGY; - psOptions->dfGeomOpParam = CPLAtofM(s.c_str()); - }) + .store_into(psOptions->dfGeomOpParam) + .action([psOptions](const std::string &) + { psOptions->eGeomOp = GEOMOP_SIMPLIFY_PRESERVE_TOPOLOGY; }) .help(_("Distance tolerance for simplification.")); argParser->add_argument("-makevalid") diff --git a/autotest/alg/data/utmsmall_lanczos_2.tif b/autotest/alg/data/utmsmall_lanczos_2.tif index fcf5c32e4c94..4d59ff20303e 100644 Binary files a/autotest/alg/data/utmsmall_lanczos_2.tif and b/autotest/alg/data/utmsmall_lanczos_2.tif differ diff --git a/autotest/cpp/CMakeLists.txt b/autotest/cpp/CMakeLists.txt index 74f10ca99290..493f1d59f9bd 100644 --- a/autotest/cpp/CMakeLists.txt +++ b/autotest/cpp/CMakeLists.txt @@ -78,6 +78,7 @@ add_executable( test_gdal_dted.cpp test_gdal_gtiff.cpp test_gdal_pixelfn.cpp + test_gdal_typetraits.cpp test_ogr.cpp test_ogr_organize_polygons.cpp test_ogr_geometry_stealing.cpp diff --git a/autotest/cpp/test_gdal_typetraits.cpp b/autotest/cpp/test_gdal_typetraits.cpp new file mode 100644 index 000000000000..90bc1e24566b --- /dev/null +++ b/autotest/cpp/test_gdal_typetraits.cpp @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: MIT +// Copyright 2024, Even Rouault + +#include "gdal_unit_test.h" + +#include "gdal_typetraits.h" + +#include "gtest_include.h" + +namespace +{ + +struct test_gdal_typetraits : public ::testing::Test +{ +}; + +TEST_F(test_gdal_typetraits, CXXTypeTraits) +{ + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_Int8); + static_assert(gdal::CXXTypeTraits::size == 1); + EXPECT_EQ( + gdal::CXXTypeTraits::GetExtendedDataType().GetNumericDataType(), + GDT_Int8); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_Byte); + static_assert(gdal::CXXTypeTraits::size == 1); + EXPECT_EQ(gdal::CXXTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Byte); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_Int16); + static_assert(gdal::CXXTypeTraits::size == 2); + EXPECT_EQ(gdal::CXXTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Int16); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_UInt16); + static_assert(gdal::CXXTypeTraits::size == 2); + EXPECT_EQ(gdal::CXXTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_UInt16); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_Int32); + static_assert(gdal::CXXTypeTraits::size == 4); + EXPECT_EQ(gdal::CXXTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Int32); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_UInt32); + static_assert(gdal::CXXTypeTraits::size == 4); + EXPECT_EQ(gdal::CXXTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_UInt32); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_Int64); + static_assert(gdal::CXXTypeTraits::size == 8); + EXPECT_EQ(gdal::CXXTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Int64); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_UInt64); + static_assert(gdal::CXXTypeTraits::size == 8); + EXPECT_EQ(gdal::CXXTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_UInt64); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_Float32); + static_assert(gdal::CXXTypeTraits::size == 4); + EXPECT_EQ( + gdal::CXXTypeTraits::GetExtendedDataType().GetNumericDataType(), + GDT_Float32); + static_assert(gdal::CXXTypeTraits::gdal_type == GDT_Float64); + static_assert(gdal::CXXTypeTraits::size == 8); + EXPECT_EQ( + gdal::CXXTypeTraits::GetExtendedDataType().GetNumericDataType(), + GDT_Float64); + static_assert(gdal::CXXTypeTraits>::gdal_type == + GDT_CFloat32); + static_assert(gdal::CXXTypeTraits>::size == 8); + EXPECT_EQ(gdal::CXXTypeTraits>::GetExtendedDataType() + .GetNumericDataType(), + GDT_CFloat32); + static_assert(gdal::CXXTypeTraits>::gdal_type == + GDT_CFloat64); + static_assert(gdal::CXXTypeTraits>::size == 16); + EXPECT_EQ(gdal::CXXTypeTraits>::GetExtendedDataType() + .GetNumericDataType(), + GDT_CFloat64); + static_assert(gdal::CXXTypeTraits::size == 0); + EXPECT_EQ( + gdal::CXXTypeTraits::GetExtendedDataType().GetClass(), + GEDTC_STRING); +} + +TEST_F(test_gdal_typetraits, GDALDataTypeTraits) +{ + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Byte); + static_assert( + std::is_same_v::type, uint8_t>); + static_assert(gdal::GDALDataTypeTraits::size == 1); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Int8); + static_assert( + std::is_same_v::type, int8_t>); + static_assert(gdal::GDALDataTypeTraits::size == 1); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Int16); + static_assert( + std::is_same_v::type, int16_t>); + static_assert(gdal::GDALDataTypeTraits::size == 2); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_UInt16); + static_assert( + std::is_same_v::type, uint16_t>); + static_assert(gdal::GDALDataTypeTraits::size == 2); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Int32); + static_assert( + std::is_same_v::type, int32_t>); + static_assert(gdal::GDALDataTypeTraits::size == 4); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_UInt32); + static_assert( + std::is_same_v::type, uint32_t>); + static_assert(gdal::GDALDataTypeTraits::size == 4); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Int64); + static_assert( + std::is_same_v::type, int64_t>); + static_assert(gdal::GDALDataTypeTraits::size == 8); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_UInt64); + static_assert( + std::is_same_v::type, uint64_t>); + static_assert(gdal::GDALDataTypeTraits::size == 8); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Float32); + static_assert( + std::is_same_v::type, float>); + static_assert(gdal::GDALDataTypeTraits::size == 4); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_Float64); + static_assert( + std::is_same_v::type, double>); + static_assert(gdal::GDALDataTypeTraits::size == 8); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_CInt16); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_CInt32); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_CFloat32); + static_assert(std::is_same_v::type, + std::complex>); + static_assert(gdal::GDALDataTypeTraits::size == 8); + EXPECT_EQ(gdal::GDALDataTypeTraits::GetExtendedDataType() + .GetNumericDataType(), + GDT_CFloat64); + static_assert(std::is_same_v::type, + std::complex>); + static_assert(gdal::GDALDataTypeTraits::size == 16); +} + +TEST_F(test_gdal_typetraits, GetOGRFieldType) +{ + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Byte), OFTInteger); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Int8), OFTInteger); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Int16), OFTInteger); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Int32), OFTInteger); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_UInt16), OFTInteger); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_UInt32), OFTInteger64); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Int64), OFTInteger64); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_UInt64), OFTReal); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Float32), OFTReal); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Float64), OFTReal); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_CInt16), OFTMaxType); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_CInt32), OFTMaxType); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_CFloat32), OFTMaxType); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_CFloat64), OFTMaxType); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_Unknown), OFTMaxType); + EXPECT_EQ(gdal::GetOGRFieldType(GDT_TypeCount), OFTMaxType); + + EXPECT_EQ(gdal::GetOGRFieldType(GDALExtendedDataType::Create(GDT_Byte)), + OFTInteger); + EXPECT_EQ(gdal::GetOGRFieldType(GDALExtendedDataType::CreateString()), + OFTString); + EXPECT_EQ( + gdal::GetOGRFieldType(GDALExtendedDataType::Create("compound", 0, {})), + OFTMaxType); +} + +} // namespace diff --git a/autotest/gcore/misc.py b/autotest/gcore/misc.py index 80c8c33613f2..cca79ca34e23 100755 --- a/autotest/gcore/misc.py +++ b/autotest/gcore/misc.py @@ -292,7 +292,6 @@ def misc_6_internal(datatype, nBands, setDriversDone): or datatype == gdal.GDT_UInt16 ): skip = True - if skip is False: dirname = "tmp/tmp/tmp_%s_%d_%s" % ( drv.ShortName, diff --git a/autotest/gcore/vrtmisc.py b/autotest/gcore/vrtmisc.py index 60c9a40a7ff8..01d0a06d7d0c 100755 --- a/autotest/gcore/vrtmisc.py +++ b/autotest/gcore/vrtmisc.py @@ -678,6 +678,26 @@ def test_vrtmisc_blocksize_gdal_translate_direct(): # Test setting block size through creation options +def test_vrtmisc_blocksize_gdalbuildvrt(): + filename = "/vsimem/test_vrtmisc_blocksize_gdalbuildvrt.vrt" + vrt_ds = gdal.BuildVRT( + filename, ["data/byte.tif"], creationOptions=["BLOCKXSIZE=32", "BLOCKYSIZE=48"] + ) + vrt_ds = None + + vrt_ds = gdal.Open(filename) + blockxsize, blockysize = vrt_ds.GetRasterBand(1).GetBlockSize() + assert blockxsize == 32 + assert blockysize == 48 + vrt_ds = None + + gdal.Unlink(filename) + + +############################################################################### +# Test setting block size through creation options + + def test_vrtmisc_blocksize_gdal_translate_indirect(): filename = "/vsimem/test_vrtmisc_blocksize_gdal_translate_indirect.vrt" vrt_ds = gdal.Translate( diff --git a/autotest/gdrivers/data/heif/uncompressed_comp_RGB_tiled.heif b/autotest/gdrivers/data/heif/uncompressed_comp_RGB_tiled.heif new file mode 100644 index 000000000000..ab30d7f2113c Binary files /dev/null and b/autotest/gdrivers/data/heif/uncompressed_comp_RGB_tiled.heif differ diff --git a/autotest/gdrivers/dimap.py b/autotest/gdrivers/dimap.py index 35568398722b..6e3378512ac3 100755 --- a/autotest/gdrivers/dimap.py +++ b/autotest/gdrivers/dimap.py @@ -233,6 +233,14 @@ def test_dimap_2_vhr2020_ms_fs(): "Red Edge", "Deep Blue", ] + assert [ds.GetRasterBand(i + 1).GetColorInterpretation() for i in range(6)] == [ + gdal.GCI_RedBand, + gdal.GCI_GreenBand, + gdal.GCI_BlueBand, + gdal.GCI_NIRBand, + gdal.GCI_RedEdgeBand, + gdal.GCI_CoastalBand, + ] rgb_ds = gdal.Open("data/dimap2/vhr2020_ms_fs/MS-FS/IMG_RGB_R1C1.TIF") ned_ds = gdal.Open("data/dimap2/vhr2020_ms_fs/MS-FS/IMG_NED_R1C1.TIF") assert ds.ReadRaster() == rgb_ds.ReadRaster() + ned_ds.ReadRaster() diff --git a/autotest/gdrivers/heif.py b/autotest/gdrivers/heif.py index 8d97e90cb43c..9bcefc45779a 100644 --- a/autotest/gdrivers/heif.py +++ b/autotest/gdrivers/heif.py @@ -13,6 +13,7 @@ # SPDX-License-Identifier: MIT ############################################################################### +import array import os import shutil @@ -32,8 +33,35 @@ def get_version(): ] +def _has_tiling_support(): + drv = gdal.GetDriverByName("HEIF") + return drv and drv.GetMetadataItem("SUPPORTS_TILES", "HEIF") + + +def _has_hevc_decoding_support(): + drv = gdal.GetDriverByName("HEIF") + return drv and drv.GetMetadataItem("SUPPORTS_HEVC", "HEIF") + + +def _has_uncompressed_decoding_support(): + drv = gdal.GetDriverByName("HEIF") + return drv and drv.GetMetadataItem("SUPPORTS_UNCOMPRESSED", "HEIF") + + +def _has_read_write_support_for(format): + drv = gdal.GetDriverByName("HEIF") + return ( + drv + and drv.GetMetadataItem("SUPPORTS_" + format, "HEIF") + and drv.GetMetadataItem("SUPPORTS_" + format + "_WRITE", "HEIF") + ) + + @pytest.mark.parametrize("endianness", ["big_endian", "little_endian"]) def test_heif_exif_endian(endianness): + if not _has_hevc_decoding_support(): + pytest.skip() + filename = "data/heif/byte_exif_%s.heic" % endianness gdal.ErrorReset() ds = gdal.Open(filename) @@ -63,6 +91,9 @@ def test_heif_exif_endian(endianness): def test_heif_thumbnail(): + if not _has_hevc_decoding_support(): + pytest.skip() + ds = gdal.Open("data/heif/byte_thumbnail.heic") assert ds assert ds.RasterXSize == 128 @@ -81,6 +112,8 @@ def test_heif_thumbnail(): def test_heif_rgb_16bit(): if get_version() < [1, 4, 0]: pytest.skip() + if not _has_hevc_decoding_support(): + pytest.skip() ds = gdal.Open("data/heif/small_world_16.heic") assert ds @@ -93,6 +126,8 @@ def test_heif_rgb_16bit(): def test_heif_rgba(): + if not _has_hevc_decoding_support(): + pytest.skip() ds = gdal.Open("data/heif/stefan_full_rgba.heic") assert ds @@ -110,6 +145,8 @@ def test_heif_rgba(): def test_heif_rgba_16bit(): if get_version() < [1, 4, 0]: pytest.skip() + if not _has_hevc_decoding_support(): + pytest.skip() ds = gdal.Open("data/heif/stefan_full_rgba_16.heic") assert ds @@ -117,7 +154,249 @@ def test_heif_rgba_16bit(): assert ds.GetRasterBand(1).DataType == gdal.GDT_UInt16 +def test_heif_tiled(): + if not _has_tiling_support(): + pytest.skip() + if not _has_uncompressed_decoding_support(): + pytest.skip() + + ds = gdal.Open("data/heif/uncompressed_comp_RGB_tiled.heif") + assert ds + assert ds.RasterXSize == 30 + assert ds.RasterYSize == 20 + assert ds.RasterCount == 3 + assert ds.GetRasterBand(1).DataType == gdal.GDT_Byte + assert ds.GetRasterBand(1).GetBlockSize() == [15, 5] + assert ds.GetRasterBand(2).GetBlockSize() == [15, 5] + assert ds.GetRasterBand(3).GetBlockSize() == [15, 5] + pytest.importorskip("osgeo.gdal_array") + assert ( + ds.GetRasterBand(1).ReadAsArray(0, 0, 30, 1) + == [ + [ + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 128, + 128, + ] + ] + ).all() + assert ( + ds.GetRasterBand(1).ReadAsArray(0, 19, 30, 1) + == [ + [ + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 128, + 128, + 128, + 128, + 255, + 255, + 255, + 255, + 238, + 238, + 238, + 238, + 255, + 255, + 255, + 255, + 0, + 0, + ] + ] + ).all() + assert ( + ds.GetRasterBand(2).ReadAsArray(0, 0, 30, 1) + == [ + [ + 0, + 0, + 0, + 0, + 128, + 128, + 128, + 128, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 128, + 128, + ] + ] + ).all() + assert ( + ds.GetRasterBand(2).ReadAsArray(0, 19, 30, 1) + == [ + [ + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 128, + 128, + 128, + 128, + 165, + 165, + 165, + 165, + 130, + 130, + 130, + 130, + 0, + 0, + 0, + 0, + 128, + 128, + ] + ] + ).all() + assert ( + ds.GetRasterBand(3).ReadAsArray(0, 0, 30, 1) + == [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 128, + 128, + ] + ] + ).all() + assert ( + ds.GetRasterBand(3).ReadAsArray(0, 19, 30, 1) + == [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 255, + 255, + 255, + 255, + 128, + 128, + 128, + 128, + 0, + 0, + 0, + 0, + 238, + 238, + 238, + 238, + 0, + 0, + 0, + 0, + 0, + 0, + ] + ] + ).all() + + def test_heif_subdatasets(tmp_path): + if not _has_hevc_decoding_support(): + pytest.skip() filename = str(tmp_path / "out.heic") shutil.copy("data/heif/subdatasets.heic", filename) @@ -214,3 +493,127 @@ def test_identify_various(major_brand, compatible_brands, expect_success): assert drv is None gdal.Unlink("/vsimem/heif_header.bin") + + +def make_data(): + ds = gdal.GetDriverByName("MEM").Create("", 300, 200, 3, gdal.GDT_Byte) + + ds.GetRasterBand(1).SetRasterColorInterpretation(gdal.GCI_RedBand) + ds.GetRasterBand(2).SetRasterColorInterpretation(gdal.GCI_GreenBand) + ds.GetRasterBand(3).SetRasterColorInterpretation(gdal.GCI_BlueBand) + + red_green_blue = ( + ([0xFF] * 100 + [0x00] * 200) + + ([0x00] * 100 + [0xFF] * 100 + [0x00] * 100) + + ([0x00] * 200 + [0xFF] * 100) + ) + rgb_bytes = array.array("B", red_green_blue).tobytes() + for line in range(100): + ds.WriteRaster( + 0, line, 300, 1, rgb_bytes, buf_type=gdal.GDT_Byte, band_list=[1, 2, 3] + ) + black_white = ([0xFF] * 150 + [0x00] * 150) * 3 + black_white_bytes = array.array("B", black_white).tobytes() + for line in range(100): + ds.WriteRaster( + 0, + 100 + line, + 300, + 1, + black_white_bytes, + buf_type=gdal.GDT_Byte, + band_list=[1, 2, 3], + ) + + assert ds.FlushCache() == gdal.CE_None + return ds + + +def make_data_with_alpha(): + ds = gdal.GetDriverByName("MEM").Create("", 300, 200, 4, gdal.GDT_Byte) + + ds.GetRasterBand(1).SetRasterColorInterpretation(gdal.GCI_RedBand) + ds.GetRasterBand(2).SetRasterColorInterpretation(gdal.GCI_GreenBand) + ds.GetRasterBand(3).SetRasterColorInterpretation(gdal.GCI_BlueBand) + ds.GetRasterBand(4).SetRasterColorInterpretation(gdal.GCI_AlphaBand) + + red_green_blue_alpha = ( + ([0xFF] * 100 + [0x00] * 200) + + ([0x00] * 100 + [0xFF] * 100 + [0x00] * 100) + + ([0x00] * 200 + [0xFF] * 100) + + ([0x7F] * 150 + [0xFF] * 150) + ) + rgba_bytes = array.array("B", red_green_blue_alpha).tobytes() + for line in range(100): + ds.WriteRaster( + 0, line, 300, 1, rgba_bytes, buf_type=gdal.GDT_Byte, band_list=[1, 2, 3, 4] + ) + black_white = ([0xFF] * 150 + [0x00] * 150) * 4 + black_white_bytes = array.array("B", black_white).tobytes() + for line in range(100): + ds.WriteRaster( + 0, + 100 + line, + 300, + 1, + black_white_bytes, + buf_type=gdal.GDT_Byte, + band_list=[1, 2, 3, 4], + ) + + assert ds.FlushCache() == gdal.CE_None + return ds + + +heif_codecs = ["AV1", "HEVC", "JPEG", "JPEG2000", "UNCOMPRESSED"] + + +@pytest.mark.parametrize("codec", heif_codecs) +def test_heif_create_copy(tmp_path, codec): + if not _has_read_write_support_for(codec): + pytest.skip() + tempfile = str(tmp_path / ("test_heif_create_copy_" + codec + ".hif")) + input_ds = make_data() + + drv = gdal.GetDriverByName("HEIF") + result_ds = drv.CreateCopy(tempfile, input_ds, options=["CODEC=" + codec]) + + result_ds = None + + result_ds = gdal.Open(tempfile) + + assert result_ds + + +@pytest.mark.parametrize("codec", heif_codecs) +def test_heif_create_copy_with_alpha(tmp_path, codec): + if not _has_read_write_support_for(codec): + pytest.skip() + tempfile = str(tmp_path / ("test_heif_create_copy_" + codec + "_alpha.hif")) + input_ds = make_data_with_alpha() + + drv = gdal.GetDriverByName("HEIF") + result_ds = drv.CreateCopy(tempfile, input_ds, options=["CODEC=" + codec]) + + result_ds = None + + result_ds = gdal.Open(tempfile) + + assert result_ds + + +def test_heif_create_copy_defaults(tmp_path): + if not _has_read_write_support_for("HEVC"): + pytest.skip() + tempfile = str(tmp_path / "test_heif_create_copy.hif") + input_ds = make_data() + + drv = gdal.GetDriverByName("HEIF") + + result_ds = drv.CreateCopy(tempfile, input_ds, options=[]) + + result_ds = None + + result_ds = gdal.Open(tempfile) + + assert result_ds diff --git a/autotest/gdrivers/jpegxl.py b/autotest/gdrivers/jpegxl.py index 27f21373acb8..4983e012b119 100755 --- a/autotest/gdrivers/jpegxl.py +++ b/autotest/gdrivers/jpegxl.py @@ -552,6 +552,24 @@ def test_jpegxl_write_five_bands(): gdal.GetDriverByName("JPEGXL").Delete(outfilename) +def test_jpegxl_write_five_bands_lossy(): + + drv = gdal.GetDriverByName("JPEGXL") + if drv.GetMetadataItem("JXL_ENCODER_SUPPORT_EXTRA_CHANNELS") is None: + pytest.skip() + + src_ds = gdal.Open("data/jpegxl/five_bands.jxl") + outfilename = "/vsimem/out.jxl" + gdal.Translate(outfilename, src_ds, options="-of JPEGXL -co DISTANCE=3 -ot Byte") + ds = gdal.Open(outfilename) + for i in range(5): + assert ds.GetRasterBand(i + 1).ComputeRasterMinMax() == pytest.approx( + (10.0 * (i + 1), 10.0 * (i + 1)), abs=1 + ) + ds = None + gdal.GetDriverByName("JPEGXL").Delete(outfilename) + + def test_jpegxl_createcopy_errors(): outfilename = "/vsimem/out.jxl" diff --git a/autotest/gdrivers/zarr_driver.py b/autotest/gdrivers/zarr_driver.py index 64e425215392..7d118e00c4d0 100644 --- a/autotest/gdrivers/zarr_driver.py +++ b/autotest/gdrivers/zarr_driver.py @@ -16,6 +16,7 @@ import base64 import json import math +import os import struct import sys @@ -5532,7 +5533,7 @@ def test_zarr_read_cf1_zarrv3(): @gdaltest.enable_exceptions() def test_zarr_write_partial_blocks_compressed(tmp_vsimem): - out_filename = "/vsimem/test.zarr" + out_filename = str(tmp_vsimem / "test.zarr") src_ds = gdal.Open("data/small_world.tif") gdal.Translate( out_filename, @@ -5541,3 +5542,45 @@ def test_zarr_write_partial_blocks_compressed(tmp_vsimem): ) out_ds = gdal.Open(out_filename) assert out_ds.ReadRaster() == src_ds.ReadRaster() + + +############################################################################### +# Test bug fix for https://github.com/OSGeo/gdal/issues/11023 + + +@gdaltest.enable_exceptions() +@pytest.mark.parametrize("format", ["ZARR_V2", "ZARR_V3"]) +def test_zarr_write_cleanup_create_dir_if_bad_blocksize(tmp_path, format): + + out_dirname = str(tmp_path / "test.zarr") + with pytest.raises(Exception): + gdal.Translate( + out_dirname, + "data/byte.tif", + options=f"-of ZARR -co FORMAT={format} -co BLOCKSIZE=1,20,20", + ) + assert not os.path.exists(out_dirname) + + +############################################################################### +# Test bug fix for https://github.com/OSGeo/gdal/issues/11023 + + +@gdaltest.enable_exceptions() +@pytest.mark.parametrize("format", ["ZARR_V2", "ZARR_V3"]) +def test_zarr_write_cleanup_create_dir_if_bad_blocksize_append_subdataset( + tmp_path, format +): + + out_dirname = str(tmp_path / "test.zarr") + gdal.Translate(out_dirname, "data/byte.tif", format="ZARR") + assert os.path.exists(out_dirname) + with pytest.raises(Exception): + gdal.Translate( + out_dirname, + "data/utm.tif", + options=f"-of ZARR -co APPEND_SUBDATASET=YES -co FORMAT={format} -co ARRAY_NAME=other -co BLOCKSIZE=1,20,20", + ) + assert os.path.exists(out_dirname) + ds = gdal.Open(out_dirname) + assert ds.GetRasterBand(1).Checksum() == 4672 diff --git a/autotest/ogr/data/pdf/recursive_resources_and_oc_name_and_empty_ocg_name.pdf b/autotest/ogr/data/pdf/recursive_resources_and_oc_name_and_empty_ocg_name.pdf new file mode 100644 index 000000000000..a2d7811825b3 Binary files /dev/null and b/autotest/ogr/data/pdf/recursive_resources_and_oc_name_and_empty_ocg_name.pdf differ diff --git a/autotest/ogr/data/xodr/empty.xodr b/autotest/ogr/data/xodr/empty.xodr new file mode 100644 index 000000000000..4668c97ba61e --- /dev/null +++ b/autotest/ogr/data/xodr/empty.xodr @@ -0,0 +1,7 @@ + + +
+
+ + +
diff --git a/autotest/ogr/ogr_geojson.py b/autotest/ogr/ogr_geojson.py index 4bc636f9e01a..d80279983b3b 100755 --- a/autotest/ogr/ogr_geojson.py +++ b/autotest/ogr/ogr_geojson.py @@ -4605,7 +4605,7 @@ def test_ogr_geojson_arrow_stream_pyarrow_mixed_timezone(tmp_vsimem): def test_ogr_geojson_arrow_stream_pyarrow_utc_plus_five(tmp_vsimem): - pytest.importorskip("pyarrow") + # pytest.importorskip("pyarrow") filename = str( tmp_vsimem / "test_ogr_geojson_arrow_stream_pyarrow_utc_plus_five.geojson" @@ -4621,22 +4621,37 @@ def test_ogr_geojson_arrow_stream_pyarrow_utc_plus_five(tmp_vsimem): lyr.CreateFeature(f) ds = None + try: + import pyarrow # NOQA + + has_pyarrow = True + except ImportError: + has_pyarrow = False + if has_pyarrow: + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + stream = lyr.GetArrowStreamAsPyArrow() + assert stream.schema.field("datetime").type.tz == "+05:00" + values = [] + for batch in stream: + for x in batch.field("datetime"): + values.append(x.value) + assert values == [1653982496789, 1653986096789] + + mem_ds = ogr.GetDriverByName("Memory").CreateDataSource("") + mem_lyr = mem_ds.CreateLayer("test", geom_type=ogr.wkbPoint) ds = ogr.Open(filename) lyr = ds.GetLayer(0) - stream = lyr.GetArrowStreamAsPyArrow() - assert stream.schema.field("datetime").type.tz == "+05:00" - values = [] - for batch in stream: - for x in batch.field("datetime"): - values.append(x.value) - assert values == [1654000496789, 1654004096789] + mem_lyr.WriteArrow(lyr) + + f = mem_lyr.GetNextFeature() + assert f["datetime"] == "2022/05/31 12:34:56.789+05" ############################################################################### def test_ogr_geojson_arrow_stream_pyarrow_utc_minus_five(tmp_vsimem): - pytest.importorskip("pyarrow") filename = str( tmp_vsimem / "test_ogr_geojson_arrow_stream_pyarrow_utc_minus_five.geojson" @@ -4652,22 +4667,37 @@ def test_ogr_geojson_arrow_stream_pyarrow_utc_minus_five(tmp_vsimem): lyr.CreateFeature(f) ds = None + try: + import pyarrow # NOQA + + has_pyarrow = True + except ImportError: + has_pyarrow = False + if has_pyarrow: + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + stream = lyr.GetArrowStreamAsPyArrow() + assert stream.schema.field("datetime").type.tz == "-05:00" + values = [] + for batch in stream: + for x in batch.field("datetime"): + values.append(x.value) + assert values == [1654018496789, 1654022096789] + + mem_ds = ogr.GetDriverByName("Memory").CreateDataSource("") + mem_lyr = mem_ds.CreateLayer("test", geom_type=ogr.wkbPoint) ds = ogr.Open(filename) lyr = ds.GetLayer(0) - stream = lyr.GetArrowStreamAsPyArrow() - assert stream.schema.field("datetime").type.tz == "-05:00" - values = [] - for batch in stream: - for x in batch.field("datetime"): - values.append(x.value) - assert values == [1654000496789, 1654004096789] + mem_lyr.WriteArrow(lyr) + + f = mem_lyr.GetNextFeature() + assert f["datetime"] == "2022/05/31 12:34:56.789-05" ############################################################################### def test_ogr_geojson_arrow_stream_pyarrow_unknown_timezone(tmp_vsimem): - pytest.importorskip("pyarrow") filename = str( tmp_vsimem / "test_ogr_geojson_arrow_stream_pyarrow_unknown_timezone.geojson" @@ -4683,15 +4713,33 @@ def test_ogr_geojson_arrow_stream_pyarrow_unknown_timezone(tmp_vsimem): lyr.CreateFeature(f) ds = None + try: + import pyarrow # NOQA + + has_pyarrow = True + except ImportError: + has_pyarrow = False + if has_pyarrow: + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + stream = lyr.GetArrowStreamAsPyArrow() + assert stream.schema.field("datetime").type.tz is None + values = [] + for batch in stream: + for x in batch.field("datetime"): + values.append(x.value) + assert values == [1654000496789, 1654004096789] + + mem_ds = ogr.GetDriverByName("Memory").CreateDataSource("") + mem_lyr = mem_ds.CreateLayer("test", geom_type=ogr.wkbPoint) ds = ogr.Open(filename) lyr = ds.GetLayer(0) - stream = lyr.GetArrowStreamAsPyArrow() - assert stream.schema.field("datetime").type.tz is None - values = [] - for batch in stream: - for x in batch.field("datetime"): - values.append(x.value) - assert values == [1654000496789, 1654004096789] + mem_lyr.WriteArrow(lyr) + + f = mem_lyr.GetNextFeature() + # We have lost the timezone info here, as there's no way in Arrow to + # have a mixed of with and without timezone in a single column + assert f["datetime"] == "2022/05/31 12:34:56.789" ############################################################################### diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index a24249c32a27..822a46334459 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -223,10 +223,9 @@ def get_sqlite_version(): ds = ogr.Open(":memory:") if ds is None: return (0, 0, 0) - sql_lyr = ds.ExecuteSQL("SELECT sqlite_version()") - f = sql_lyr.GetNextFeature() - version = f.GetField(0) - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT sqlite_version()") as sql_lyr: + f = sql_lyr.GetNextFeature() + version = f.GetField(0) return tuple([int(x) for x in version.split(".")[0:3]]) @@ -371,11 +370,10 @@ def test_ogr_gpkg_3(gpkg_ds, tmp_path): assert lyr1.GetName() == "a_layer", "unexpected layer name for layer 1" - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT * FROM sqlite_master WHERE name = 'gpkg_extensions'" - ) - assert sql_lyr.GetFeatureCount() == 0 - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 ############################################################################### @@ -513,13 +511,12 @@ def test_ogr_gpkg_7(gpkg_ds): assert lyr.GetFeatureCount() == 2 def get_feature_count_from_gpkg_contents(): - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( 'SELECT feature_count FROM gpkg_ogr_contents WHERE table_name = "field_test_layer"', dialect="DEBUG", - ) - f = sql_lyr.GetNextFeature() - ret = f.GetField(0) - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + ret = f.GetField(0) return ret assert get_feature_count_from_gpkg_contents() == 2 @@ -739,21 +736,20 @@ def test_ogr_gpkg_12(gpkg_ds): gpkg_ds.ExecuteSQL("ALTER TABLE tbl_linestring RENAME TO tbl_linestring_renamed;") - sql_lyr = gpkg_ds.ExecuteSQL("SELECT * FROM tbl_linestring_renamed") - assert sql_lyr.GetFIDColumn() == "fid" - assert sql_lyr.GetGeomType() == ogr.wkbLineString - assert sql_lyr.GetGeometryColumn() == "geom" - assert sql_lyr.GetSpatialRef().ExportToWkt().find("32631") >= 0 - feat = sql_lyr.GetNextFeature() - assert feat.GetFID() == 1 - assert sql_lyr.GetFeatureCount() == 10 - assert sql_lyr.GetLayerDefn().GetFieldCount() == 10 - assert sql_lyr.GetLayerDefn().GetFieldDefn(6).GetSubType() == ogr.OFSTBoolean - assert sql_lyr.GetLayerDefn().GetFieldDefn(7).GetSubType() == ogr.OFSTInt16 - assert sql_lyr.GetLayerDefn().GetFieldDefn(8).GetSubType() == ogr.OFSTFloat32 - gpkg_ds.ReleaseResultSet(sql_lyr) - - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL("SELECT * FROM tbl_linestring_renamed") as sql_lyr: + assert sql_lyr.GetFIDColumn() == "fid" + assert sql_lyr.GetGeomType() == ogr.wkbLineString + assert sql_lyr.GetGeometryColumn() == "geom" + assert sql_lyr.GetSpatialRef().ExportToWkt().find("32631") >= 0 + feat = sql_lyr.GetNextFeature() + assert feat.GetFID() == 1 + assert sql_lyr.GetFeatureCount() == 10 + assert sql_lyr.GetLayerDefn().GetFieldCount() == 10 + assert sql_lyr.GetLayerDefn().GetFieldDefn(6).GetSubType() == ogr.OFSTBoolean + assert sql_lyr.GetLayerDefn().GetFieldDefn(7).GetSubType() == ogr.OFSTInt16 + assert sql_lyr.GetLayerDefn().GetFieldDefn(8).GetSubType() == ogr.OFSTFloat32 + + with gpkg_ds.ExecuteSQL( "SELECT " "CAST(fid AS INTEGER) AS FID, " "CAST(fid AS INTEGER) AS FID, " @@ -767,46 +763,44 @@ def test_ogr_gpkg_12(gpkg_ds): "CAST(fld_binary as BLOB) as FLD_BINARY, " "CAST(fld_integer64 AS INTEGER) AS FLD_INTEGER64 " "FROM tbl_linestring_renamed" - ) - assert sql_lyr.GetFIDColumn() == "FID" - assert sql_lyr.GetGeometryColumn() == "GEOM" - assert sql_lyr.GetLayerDefn().GetFieldCount() == 5 - assert sql_lyr.GetLayerDefn().GetFieldDefn(0).GetName() == "FLD_INTEGER" - assert sql_lyr.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTInteger - assert sql_lyr.GetLayerDefn().GetFieldDefn(1).GetName() == "FLD_STRING" - assert sql_lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTString - assert sql_lyr.GetLayerDefn().GetFieldDefn(2).GetName() == "FLD_REAL" - assert sql_lyr.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTReal - assert sql_lyr.GetLayerDefn().GetFieldDefn(3).GetName() == "FLD_BINARY" - assert sql_lyr.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTBinary - assert sql_lyr.GetLayerDefn().GetFieldDefn(4).GetName() == "FLD_INTEGER64" - assert sql_lyr.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTInteger64 - gpkg_ds.ReleaseResultSet(sql_lyr) - - sql_lyr = gpkg_ds.ExecuteSQL("SELECT * FROM tbl_linestring_renamed WHERE 0=1") - feat = sql_lyr.GetNextFeature() - assert feat is None - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFIDColumn() == "FID" + assert sql_lyr.GetGeometryColumn() == "GEOM" + assert sql_lyr.GetLayerDefn().GetFieldCount() == 5 + assert sql_lyr.GetLayerDefn().GetFieldDefn(0).GetName() == "FLD_INTEGER" + assert sql_lyr.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTInteger + assert sql_lyr.GetLayerDefn().GetFieldDefn(1).GetName() == "FLD_STRING" + assert sql_lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTString + assert sql_lyr.GetLayerDefn().GetFieldDefn(2).GetName() == "FLD_REAL" + assert sql_lyr.GetLayerDefn().GetFieldDefn(2).GetType() == ogr.OFTReal + assert sql_lyr.GetLayerDefn().GetFieldDefn(3).GetName() == "FLD_BINARY" + assert sql_lyr.GetLayerDefn().GetFieldDefn(3).GetType() == ogr.OFTBinary + assert sql_lyr.GetLayerDefn().GetFieldDefn(4).GetName() == "FLD_INTEGER64" + assert sql_lyr.GetLayerDefn().GetFieldDefn(4).GetType() == ogr.OFTInteger64 + + with gpkg_ds.ExecuteSQL( + "SELECT * FROM tbl_linestring_renamed WHERE 0=1" + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat is None for sql in [ "SELECT * FROM tbl_linestring_renamed LIMIT 1", "SELECT * FROM tbl_linestring_renamed ORDER BY fld_integer LIMIT 1", "SELECT * FROM tbl_linestring_renamed UNION ALL SELECT * FROM tbl_linestring_renamed ORDER BY fld_integer LIMIT 1", ]: - sql_lyr = gpkg_ds.ExecuteSQL(sql) + with gpkg_ds.ExecuteSQL(sql) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat is not None + feat = sql_lyr.GetNextFeature() + assert feat is None + assert sql_lyr.GetFeatureCount() == 1 + + with gpkg_ds.ExecuteSQL("SELECT sqlite_version()") as sql_lyr: feat = sql_lyr.GetNextFeature() assert feat is not None - feat = sql_lyr.GetNextFeature() - assert feat is None - assert sql_lyr.GetFeatureCount() == 1 - gpkg_ds.ReleaseResultSet(sql_lyr) - - sql_lyr = gpkg_ds.ExecuteSQL("SELECT sqlite_version()") - feat = sql_lyr.GetNextFeature() - assert feat is not None - assert sql_lyr.GetLayerDefn().GetFieldCount() == 1 - assert sql_lyr.GetLayerDefn().GetGeomFieldCount() == 0 - gpkg_ds.ReleaseResultSet(sql_lyr) + assert sql_lyr.GetLayerDefn().GetFieldCount() == 1 + assert sql_lyr.GetLayerDefn().GetGeomFieldCount() == 0 ############################################################################### @@ -864,14 +858,12 @@ def test_ogr_gpkg_14(gpkg_ds): assert f.GetGeometryRef().ExportToWkt() == "POINT EMPTY" f = None - sql_lyr = gpkg_ds.ExecuteSQL('SELECT * FROM "point_no_spi-but-with-dashes"') - res = sql_lyr.TestCapability(ogr.OLCFastSpatialFilter) - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL('SELECT * FROM "point_no_spi-but-with-dashes"') as sql_lyr: + res = sql_lyr.TestCapability(ogr.OLCFastSpatialFilter) assert res == 0 - sql_lyr = gpkg_ds.ExecuteSQL('SELECT * FROM "point-with-spi-and-dashes"') - res = sql_lyr.TestCapability(ogr.OLCFastSpatialFilter) - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL('SELECT * FROM "point-with-spi-and-dashes"') as sql_lyr: + res = sql_lyr.TestCapability(ogr.OLCFastSpatialFilter) assert res == 1 # Test spatial filer right away @@ -911,40 +903,38 @@ def test_ogr_gpkg_15(gpkg_ds): gpkg_ds = gdaltest.reopen(gpkg_ds, update=1) - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_IsEmpty(geom), ST_SRID(geom), ST_GeometryType(geom), " + 'ST_MinX(geom), ST_MinY(geom), ST_MaxX(geom), ST_MaxY(geom) FROM "point_no_spi-but-with-dashes" WHERE fid = 1' - ) - feat = sql_lyr.GetNextFeature() - assert feat is not None - assert feat.GetField(0) == 0 - assert feat.GetField(1) == 32631 - assert feat.GetField(2) == "POINT" - assert feat.GetField(3) == 1000 - assert feat.GetField(4) == 30000000 - assert feat.GetField(5) == 1000 - assert feat.GetField(6) == 30000000 - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat is not None + assert feat.GetField(0) == 0 + assert feat.GetField(1) == 32631 + assert feat.GetField(2) == "POINT" + assert feat.GetField(3) == 1000 + assert feat.GetField(4) == 30000000 + assert feat.GetField(5) == 1000 + assert feat.GetField(6) == 30000000 # add an empty feature to tbl_linestring lyr = gpkg_ds.GetLayer("tbl_linestring") feat = ogr.Feature(lyr.GetLayerDefn()) assert lyr.CreateFeature(feat) == 0 - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_IsEmpty(geom), ST_SRID(geom), ST_GeometryType(geom), " + "ST_MinX(geom), ST_MinY(geom), ST_MaxX(geom), ST_MaxY(geom) FROM tbl_linestring WHERE geom IS NULL" - ) - feat = sql_lyr.GetNextFeature() - assert feat is not None - assert feat.IsFieldNull(0) - assert feat.IsFieldNull(1) - assert feat.IsFieldNull(2) - assert feat.IsFieldNull(3) - assert feat.IsFieldNull(4) - assert feat.IsFieldNull(5) - assert feat.IsFieldNull(6) - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat is not None + assert feat.IsFieldNull(0) + assert feat.IsFieldNull(1) + assert feat.IsFieldNull(2) + assert feat.IsFieldNull(3) + assert feat.IsFieldNull(4) + assert feat.IsFieldNull(5) + assert feat.IsFieldNull(6) for (expected_type, actual_type, expected_result) in [ ("POINT", "POINT", 1), @@ -954,12 +944,12 @@ def test_ogr_gpkg_15(gpkg_ds): ("GEOMETRYCOLLECTION", "MULTIPOINT", 1), ("GEOMETRYCOLLECTION", "POINT", 0), ]: - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT GPKG_IsAssignable('%s', '%s')" % (expected_type, actual_type) - ) - feat = sql_lyr.GetNextFeature() - got_result = feat.GetField(0) - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + got_result = feat.GetField(0) + assert ( got_result == expected_result ), "expected_type=%s actual_type=%s expected_result=%d got_result=%d" % ( @@ -1000,116 +990,87 @@ def test_ogr_gpkg_15(gpkg_ds): ]: if expected_result == 0: gdal.PushErrorHandler("CPLQuietErrorHandler") - sql_lyr = gpkg_ds.ExecuteSQL(sql) - if expected_result == 0: - gdal.PopErrorHandler() - feat = sql_lyr.GetNextFeature() - got_result = feat.GetField(0) - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL(sql) as sql_lyr: + if expected_result == 0: + gdal.PopErrorHandler() + feat = sql_lyr.GetNextFeature() + got_result = feat.GetField(0) + assert got_result == expected_result, sql # NULL argument - sql_lyr = gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS(NULL, 4326)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != -1: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS(NULL, 4326)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == -1 # NULL argument - sql_lyr = gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS('epsg', NULL)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != -1: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS('epsg', NULL)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == -1 # Existing entry - sql_lyr = gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS('epsg', 4326)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != 4326: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS('epsg', 4326)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == 4326 # Non existing entry - sql_lyr = gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS('epsg', 1234)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != -1: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT SridFromAuthCRS('epsg', 1234)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == -1 # NULL argument - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(NULL)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != -1: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(NULL)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == -1 # Existing entry in gpkg_spatial_ref_sys - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(4326)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != 4326: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(4326)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == 4326 # New entry in gpkg_spatial_ref_sys - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(32633)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != 32633: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(32633)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == 32633 # Invalid code with gdal.quiet_errors(): - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(0)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != -1: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ImportFromEPSG(0)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == -1 # NULL argument - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_Transform(NULL, 4326)") - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef() is not None: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ST_Transform(NULL, 4326)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetGeometryRef() is None # Invalid geometry with gdal.quiet_errors(): - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_Transform(x'00', 4326)") - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef() is not None: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ST_Transform(x'00', 4326)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetGeometryRef() is None # NULL argument - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_Transform(geom, NULL) FROM tbl_linestring") - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef() is not None: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL( + "SELECT ST_Transform(geom, NULL) FROM tbl_linestring" + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetGeometryRef() is None # Invalid target SRID=0 # GeoPackage: The record with an srs_id of 0 SHALL be used for undefined geographic coordinate reference systems. with gdal.quiet_errors(): - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_Transform(geom, 0), ST_SRID(ST_Transform(geom, 0)) FROM tbl_linestring" - ) - assert sql_lyr.GetSpatialRef().ExportToWkt().find("Undefined geographic SRS") >= 0 - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef() is None or feat.GetField(0) != 0: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert ( + sql_lyr.GetSpatialRef().ExportToWkt().find("Undefined geographic SRS") + >= 0 + ) + feat = sql_lyr.GetNextFeature() + if feat.GetGeometryRef() is None or feat.GetField(0) != 0: + feat.DumpReadable() + pytest.fail() # Invalid source SRID=0 # GeoPackage: The record with an srs_id of 0 SHALL be used for undefined geographic coordinate reference systems. @@ -1118,183 +1079,126 @@ def test_ogr_gpkg_15(gpkg_ds): src_lyr = gpkg_ds.GetLayerByName("point-with-spi-and-dashes") assert src_lyr.GetSpatialRef().ExportToWkt().find("Undefined geographic SRS") >= 0 with gdal.quiet_errors(): - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( 'SELECT ST_Transform(geom, 4326), ST_SRID(ST_Transform(geom, 4326)) FROM "point-with-spi-and-dashes"' - ) - assert sql_lyr.GetSpatialRef().ExportToWkt().find("WGS_1984") >= 0 - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef() is None or feat.GetField(0) != 4326: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetSpatialRef().ExportToWkt().find("WGS_1984") >= 0 + feat = sql_lyr.GetNextFeature() + if feat.GetGeometryRef() is None or feat.GetField(0) != 4326: + feat.DumpReadable() + pytest.fail() # Invalid spatialite geometry: SRID=4326,MULTIPOINT EMPTY truncated with gdal.quiet_errors(): - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_Transform(x'0001E610000000000000000000000000000000000000000000000000000000000000000000007C04000000000000FE', 4326) FROM tbl_linestring" - ) - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef() is not None: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetGeometryRef() is None - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_Transform(geom, ST_SRID(geom)) FROM tbl_linestring" - ) - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef().ExportToWkt() != "LINESTRING (5 5,10 5,10 10,5 10)": - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + if feat.GetGeometryRef().ExportToWkt() != "LINESTRING (5 5,10 5,10 10,5 10)": + feat.DumpReadable() + pytest.fail() - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_SRID(ST_Transform(geom, 4326)) FROM tbl_linestring" - ) - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != 4326: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == 4326 # Spatialite geometry: SRID=4326,MULTIPOINT EMPTY - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_SRID(ST_Transform(x'0001E610000000000000000000000000000000000000000000000000000000000000000000007C0400000000000000FE', 4326)) FROM tbl_linestring" - ) - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != 4326: - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == 4326 # Error case: less than 8 bytes - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_MinX(x'00')") - feat = sql_lyr.GetNextFeature() - if feat.IsFieldSetAndNotNull(0): - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ST_MinX(x'00')") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert not feat.IsFieldSetAndNotNull(0) # Error case: 8 wrong bytes - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_MinX(x'0001020304050607')") - feat = sql_lyr.GetNextFeature() - if feat.IsFieldSetAndNotNull(0): - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ST_MinX(x'0001020304050607')") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert not feat.IsFieldSetAndNotNull(0) # Error case: too short blob - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_GeometryType(x'4750001100000000')") - feat = sql_lyr.GetNextFeature() - if feat.IsFieldSetAndNotNull(0): - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ST_GeometryType(x'4750001100000000')") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert not feat.IsFieldSetAndNotNull(0) # Error case: too short blob - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_GeometryType(x'475000110000000001040000')") - feat = sql_lyr.GetNextFeature() - if feat.IsFieldSetAndNotNull(0): - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL( + "SELECT ST_GeometryType(x'475000110000000001040000')" + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert not feat.IsFieldSetAndNotNull(0) # Invalid geometry, but long enough for our purpose... - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_GeometryType(x'47500011000000000104000000')" - ) - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != "MULTIPOINT": - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + if feat.GetField(0) != "MULTIPOINT": + feat.DumpReadable() + pytest.fail() # Spatialite geometry (MULTIPOINT EMPTY) - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_GeometryType(x'00010000000000000000000000000000000000000000000000000000000000000000000000007C0400000000000000FE')" - ) - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != "MULTIPOINT": - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + if feat.GetField(0) != "MULTIPOINT": + feat.DumpReadable() + pytest.fail() # Spatialite geometry (MULTIPOINT EMPTY) - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_IsEmpty(x'00010000000000000000000000000000000000000000000000000000000000000000000000007C0400000000000000FE')" - ) - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != 1: - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == 1 # Error case: invalid geometry with gdal.quiet_errors(): - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_GeometryType(x'475000030000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')" - ) - feat = sql_lyr.GetNextFeature() - if feat.IsFieldSetAndNotNull(0): - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert not feat.IsFieldSetAndNotNull(0) # Error case: invalid type - sql_lyr = gpkg_ds.ExecuteSQL("SELECT GPKG_IsAssignable('POINT', NULL)") - feat = sql_lyr.GetNextFeature() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT GPKG_IsAssignable('POINT', NULL)") as sql_lyr: + sql_lyr.GetNextFeature() # Error case: invalid type - sql_lyr = gpkg_ds.ExecuteSQL("SELECT GPKG_IsAssignable(NULL, 'POINT')") - feat = sql_lyr.GetNextFeature() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT GPKG_IsAssignable(NULL, 'POINT')") as sql_lyr: + sql_lyr.GetNextFeature() # Test hstore_get_value - sql_lyr = gpkg_ds.ExecuteSQL("SELECT hstore_get_value('a=>b', 'a')") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) != "b": - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT hstore_get_value('a=>b', 'a')") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == "b" # Test hstore_get_value - sql_lyr = gpkg_ds.ExecuteSQL("SELECT hstore_get_value('a=>b', 'x')") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) is not None: - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT hstore_get_value('a=>b', 'x')") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) is None # Error case: invalid type - sql_lyr = gpkg_ds.ExecuteSQL("SELECT hstore_get_value('a=>b', NULL)") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) is not None: - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT hstore_get_value('a=>b', NULL)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) is None # Error case: invalid type - sql_lyr = gpkg_ds.ExecuteSQL("SELECT hstore_get_value(NULL, 'a')") - feat = sql_lyr.GetNextFeature() - if feat.GetField(0) is not None: - feat.DumpReadable() - pytest.fail() - feat = None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT hstore_get_value(NULL, 'a')") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) is None if ( ogr.GetGEOSVersionMajor() * 10000 @@ -1302,30 +1206,29 @@ def test_ogr_gpkg_15(gpkg_ds): + ogr.GetGEOSVersionMicro() >= 30800 ): - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_MakeValid(NULL)") - feat = sql_lyr.GetNextFeature() - assert feat.GetGeometryRef() is None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ST_MakeValid(NULL)") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetGeometryRef() is None - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_MakeValid('invalid')") - feat = sql_lyr.GetNextFeature() - assert feat.GetGeometryRef() is None - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT ST_MakeValid('invalid')") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetGeometryRef() is None - sql_lyr = gpkg_ds.ExecuteSQL("SELECT ST_MakeValid(geom) FROM tbl_linestring") - feat = sql_lyr.GetNextFeature() - if feat.GetGeometryRef().ExportToWkt() != "LINESTRING (5 5,10 5,10 10,5 10)": - feat.DumpReadable() - pytest.fail() - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL( + "SELECT ST_MakeValid(geom) FROM tbl_linestring" + ) as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert ( + feat.GetGeometryRef().ExportToWkt() + == "LINESTRING (5 5,10 5,10 10,5 10)" + ) if _has_spatialite_4_3_or_later(gpkg_ds): - sql_lyr = gpkg_ds.ExecuteSQL( + with gpkg_ds.ExecuteSQL( "SELECT ST_Buffer(geom, 1e-10) FROM tbl_linestring" - ) - assert sql_lyr.GetGeomType() == ogr.wkbPolygon - assert sql_lyr.GetSpatialRef().ExportToWkt().find("32631") >= 0 - gpkg_ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetGeomType() == ogr.wkbPolygon + assert sql_lyr.GetSpatialRef().ExportToWkt().find("32631") >= 0 ############################################################################### @@ -1342,19 +1245,15 @@ def test_ogr_gpkg_SetSRID(tmp_vsimem): lyr.CreateFeature(f) for srid in (32631, 0, -1, 12345678): - sql_lyr = ds.ExecuteSQL("SELECT ST_SRID(SetSRID(geom, %d)) FROM foo" % srid) - f = sql_lyr.GetNextFeature() - try: + with ds.ExecuteSQL( + "SELECT ST_SRID(SetSRID(geom, %d)) FROM foo" % srid + ) as sql_lyr: + f = sql_lyr.GetNextFeature() assert f.GetField(0) == srid, srid - finally: - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL("SELECT ST_SRID(SetSRID(NULL, 32631)) FROM foo") - f = sql_lyr.GetNextFeature() - try: + with ds.ExecuteSQL("SELECT ST_SRID(SetSRID(NULL, 32631)) FROM foo") as sql_lyr: + f = sql_lyr.GetNextFeature() assert f.GetField(0) is None - finally: - ds.ReleaseResultSet(sql_lyr) ds = None @@ -1377,7 +1276,7 @@ def test_ogr_gpkg_ST_EnvIntersects(tmp_vsimem): f.SetGeometry(ogr.CreateGeometryFromWkt("LINESTRING(5 6,7 8)")) lyr.CreateFeature(f) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT ST_EnvIntersects(geom, 0, 0, 0.99, 100)," + " ST_EnvIntersects(geom, 0, 4.01, 100, 100)," + " ST_EnvIntersects(geom, 3.01, 0, 100, 100)," @@ -1387,9 +1286,8 @@ def test_ogr_gpkg_ST_EnvIntersects(tmp_vsimem): + " ST_EnvIntersects(geom, 2.99, 3.99, 3.01, 4.01)," + " ST_EnvIntersects(geom, 2.99, 1.99, 3.01, 2.01)" + " FROM foo WHERE fid = 1" - ) - f = sql_lyr.GetNextFeature() - try: + ) as sql_lyr: + f = sql_lyr.GetNextFeature() assert f.GetField(0) == 0 assert f.GetField(1) == 0 assert f.GetField(2) == 0 @@ -1398,35 +1296,24 @@ def test_ogr_gpkg_ST_EnvIntersects(tmp_vsimem): assert f.GetField(5) == 1 assert f.GetField(6) == 1 assert f.GetField(7) == 1 - finally: - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT ST_EnvIntersects(a.geom, b.geom) FROM foo a, foo b WHERE a.fid = 1 AND b.fid = 1" - ) - f = sql_lyr.GetNextFeature() - try: + ) as sql_lyr: + f = sql_lyr.GetNextFeature() assert f.GetField(0) == 1 - finally: - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT ST_EnvIntersects(a.geom, b.geom) FROM foo a, foo b WHERE a.fid = 1 AND b.fid = 2" - ) - f = sql_lyr.GetNextFeature() - try: + ) as sql_lyr: + f = sql_lyr.GetNextFeature() assert f.GetField(0) == 0 - finally: - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT ST_EnvIntersects(a.geom, b.geom) FROM foo a, foo b WHERE a.fid = 2 AND b.fid = 1" - ) - f = sql_lyr.GetNextFeature() - try: + ) as sql_lyr: + f = sql_lyr.GetNextFeature() assert f.GetField(0) == 0 - finally: - ds.ReleaseResultSet(sql_lyr) ds = None @@ -1559,10 +1446,9 @@ def test_ogr_gpkg_16b(tmp_vsimem): def test_ogr_gpkg_17(tmp_vsimem): ds = gdaltest.gpkg_dr.CreateDataSource(tmp_vsimem / "ogr_gpkg_17.gpkg") - sql_lyr = ds.ExecuteSQL("SELECT ogr_version()", dialect="INDIRECT_SQLITE") - f = sql_lyr.GetNextFeature() - assert f is not None - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT ogr_version()", dialect="INDIRECT_SQLITE") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f is not None ds = None @@ -1594,11 +1480,10 @@ def test_ogr_gpkg_18(tmp_vsimem, tmp_path): g = f.GetGeometryRef() assert g.GetGeometryType() == ogr.wkbCircularString - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_extensions WHERE table_name = 'wkbCircularString' AND extension_name = 'gpkg_geom_CIRCULARSTRING'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 ds = None @@ -1614,11 +1499,10 @@ def test_ogr_gpkg_18a(tmp_vsimem): lyr.CreateFeature(f) f = None - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_extensions WHERE table_name = 'test' AND extension_name = 'gpkg_geom_CIRCULARSTRING'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 ds = None @@ -1654,11 +1538,10 @@ def test_ogr_gpkg_18b(tmp_vsimem, tmp_path): with gdal.quiet_errors(): # Warning 1: Registering non-standard gpkg_geom_TRIANGLE extension ds.FlushCache() - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_extensions WHERE table_name = 'test' AND extension_name = 'gpkg_geom_TRIANGLE'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 ds = None if has_validate(): @@ -1679,11 +1562,10 @@ def test_ogr_gpkg_18c(tmp_vsimem): ) lyr.CreateFeature(f) f = None - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_extensions WHERE table_name = 'test' AND extension_name LIKE 'gpkg_geom_%'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 ds = None @@ -1736,11 +1618,10 @@ def test_ogr_gpkg_19(tmp_vsimem, tmp_path): ds = ogr.Open(fname) # Check that we don't create triggers - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM sqlite_master WHERE type = 'trigger' AND tbl_name IN ('gpkg_metadata', 'gpkg_metadata_reference')" - ) - assert sql_lyr.GetFeatureCount() == 0 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 lyr = ds.GetLayer("test_with_md") assert lyr.GetMetadataItem("IDENTIFIER") == "ident" @@ -1828,18 +1709,16 @@ def test_ogr_gpkg_20(tmp_vsimem, tmp_path): ds = ogr.Open(fname) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_name='my geogcs' AND srs_id = 100000 AND organization='MY_ORG' AND organization_coordsys_id=4326 AND description is NULL" - ) - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 1 - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_name='another geogcs' AND srs_id = 100001 AND organization='NONE' AND organization_coordsys_id=100001 AND description is NULL" - ) - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 1 lyr = ds.GetLayer("my_org_4326") @@ -1929,20 +1808,18 @@ def test_ogr_gpkg_srs_non_duplication_custom_crs(tmp_vsimem): lyr = ds.CreateLayer("test2", srs=srs) assert lyr - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys") - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys") as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 4 # srs_id 0, 1, 4326 + custom one - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_name='my custom geogcs'" - ) - assert sql_lyr.GetFeatureCount() == 1 - f = sql_lyr.GetNextFeature() - assert f["srs_id"] == 100000 - assert f["organization"] == "NONE" - assert f["organization_coordsys_id"] == 100000 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + f = sql_lyr.GetNextFeature() + assert f["srs_id"] == 100000 + assert f["organization"] == "NONE" + assert f["organization_coordsys_id"] == 100000 # Test now transitionning to definition_12_063 / WKT2 database structure... srs_3d = osr.SpatialReference() @@ -1969,21 +1846,19 @@ def test_ogr_gpkg_srs_non_duplication_custom_crs(tmp_vsimem): lyr = ds.CreateLayer("test_3d_bis", srs=srs_3d) assert lyr - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_name='srs 3d'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 # Test again with SRS that can be represented in WKT1 lyr = ds.CreateLayer("test3", srs=srs) assert lyr - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_name='my custom geogcs'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 ds = None @@ -2035,15 +1910,14 @@ def test_ogr_gpkg_srs_non_consistent_with_official_definition(tmp_vsimem): == 'GEOGCS["my geogcs 4267",DATUM["WGS_1984",SPHEROID["my spheroid",1000,0]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4267"]]' ) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_name='my geogcs 4267'" - ) - assert sql_lyr.GetFeatureCount() == 1 - f = sql_lyr.GetNextFeature() - assert f["srs_id"] == 100000 - assert f["organization"] == "NONE" - assert f["organization_coordsys_id"] == 100000 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + f = sql_lyr.GetNextFeature() + assert f["srs_id"] == 100000 + assert f["organization"] == "NONE" + assert f["organization_coordsys_id"] == 100000 lyr = ds.GetLayer("test_fake_4326") assert ( @@ -2051,28 +1925,25 @@ def test_ogr_gpkg_srs_non_consistent_with_official_definition(tmp_vsimem): == 'GEOGCS["my geogcs 4326",DATUM["WGS_1984",SPHEROID["my spheroid",1000,0]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]' ) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_spatial_ref_sys WHERE srs_name='my geogcs 4326'" - ) - assert sql_lyr.GetFeatureCount() == 1 - f = sql_lyr.GetNextFeature() - assert f["srs_id"] == 100001 - assert f["organization"] == "NONE" - assert f["organization_coordsys_id"] == 100001 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + f = sql_lyr.GetNextFeature() + assert f["srs_id"] == 100001 + assert f["organization"] == "NONE" + assert f["organization_coordsys_id"] == 100001 - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys") - fc_before = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys") as sql_lyr: + fc_before = sql_lyr.GetFeatureCount() gdal.ErrorReset() gdal.ErrorReset() lyr = ds.CreateLayer("test_fake_4267_bis", srs=test_fake_4267) assert gdal.GetLastErrorMsg() == "" - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys") - fc_after = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys") as sql_lyr: + fc_after = sql_lyr.GetFeatureCount() assert fc_before == fc_after ds = None @@ -2086,10 +1957,9 @@ def test_ogr_gpkg_write_srs_undefined_geographic(tmp_path): assert gpkg_ds is not None # Check initial default SRS entries in gpkg_spatial_ref_sys - sql_lyr = gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") - gpkg_spatial_ref_sys_total = sql_lyr.GetNextFeature().GetField(0) - assert gpkg_spatial_ref_sys_total == 3 # entries with SRS IDs: -1, 0, 4326 - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") as sql_lyr: + gpkg_spatial_ref_sys_total = sql_lyr.GetNextFeature().GetField(0) + assert gpkg_spatial_ref_sys_total == 3 # entries with SRS IDs: -1, 0, 4326 srs = osr.SpatialReference() srs.SetFromUserInput( @@ -2106,9 +1976,8 @@ def test_ogr_gpkg_write_srs_undefined_geographic(tmp_path): gpkg_ds = ogr.Open(fname) # Check no new SRS entries have been inserted into gpkg_spatial_ref_sys - sql_lyr = gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") - assert gpkg_spatial_ref_sys_total == sql_lyr.GetNextFeature().GetField(0) - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") as sql_lyr: + assert gpkg_spatial_ref_sys_total == sql_lyr.GetNextFeature().GetField(0) lyr = gpkg_ds.GetLayer(0) srs_wkt = lyr.GetSpatialRef().ExportToWkt() @@ -2126,10 +1995,9 @@ def test_ogr_gpkg_write_srs_undefined_Cartesian(tmp_path): assert gpkg_ds is not None # Check initial default SRS entries in gpkg_spatial_ref_sys - sql_lyr = gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") - gpkg_spatial_ref_sys_total = sql_lyr.GetNextFeature().GetField(0) - assert gpkg_spatial_ref_sys_total == 3 # SRS with IDs: -1, 0, 4326 - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") as sql_lyr: + gpkg_spatial_ref_sys_total = sql_lyr.GetNextFeature().GetField(0) + assert gpkg_spatial_ref_sys_total == 3 # SRS with IDs: -1, 0, 4326 srs = osr.SpatialReference() srs.SetFromUserInput('LOCAL_CS["Undefined Cartesian SRS"]') @@ -2144,9 +2012,8 @@ def test_ogr_gpkg_write_srs_undefined_Cartesian(tmp_path): gpkg_ds = ogr.Open(fname) # Check no new SRS entries have been inserted into gpkg_spatial_ref_sys - sql_lyr = gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") - assert gpkg_spatial_ref_sys_total == sql_lyr.GetNextFeature().GetField(0) - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("SELECT COUNT(*) FROM gpkg_spatial_ref_sys") as sql_lyr: + assert gpkg_spatial_ref_sys_total == sql_lyr.GetNextFeature().GetField(0) lyr = gpkg_ds.GetLayer(0) srs_wkt = lyr.GetSpatialRef().ExportToWkt() @@ -2497,38 +2364,40 @@ def test_ogr_gpkg_23(tmp_vsimem): field_defn = ogr.FieldDefn("field_nullable", ogr.OFTString) lyr.CreateField(field_defn) - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_contents WHERE data_type = 'features'") - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL( + "SELECT * FROM gpkg_contents WHERE data_type = 'features'" + ) as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 2 - sql_lyr = ds.ExecuteSQL("SELECT 1 FROM sqlite_master WHERE name='gpkg_extensions'") - has_gpkg_extensions = sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL( + "SELECT 1 FROM sqlite_master WHERE name='gpkg_extensions'" + ) as sql_lyr: + has_gpkg_extensions = sql_lyr.GetFeatureCount() == 1 assert not has_gpkg_extensions - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 2 field_defn = ogr.GeomFieldDefn("geomfield_not_nullable", ogr.wkbPoint) field_defn.SetNullable(0) lyr.CreateGeomField(field_defn) - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_contents WHERE data_type = 'features'") - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL( + "SELECT * FROM gpkg_contents WHERE data_type = 'features'" + ) as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 3 - sql_lyr = ds.ExecuteSQL("SELECT 1 FROM sqlite_master WHERE name='gpkg_extensions'") - has_gpkg_extensions = sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL( + "SELECT 1 FROM sqlite_master WHERE name='gpkg_extensions'" + ) as sql_lyr: + has_gpkg_extensions = sql_lyr.GetFeatureCount() == 1 assert not has_gpkg_extensions - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 3 f = ogr.Feature(lyr.GetLayerDefn()) @@ -3080,7 +2949,8 @@ def test_ogr_gpkg_26(tmp_vsimem): ret = lyr.CreateFeature(f) # ds.CommitTransaction() - ds.ReleaseResultSet(ds.ExecuteSQL("SELECT 1")) + with ds.ExecuteSQL("SELECT 1") as sql_lyr: + assert sql_lyr # ds = None # ds = ogr.Open('/vsimem/ogr_gpkg_26.gpkg', update = 1) # lyr = ds.GetLayerByName('test3') @@ -3117,12 +2987,9 @@ def test_ogr_gpkg_27(tmp_vsimem): f = ogr.Feature(lyr.GetLayerDefn()) f.SetGeometry(ogr.CreateGeometryFromWkt("POINT (2 49)")) lyr.CreateFeature(f) - sql_lyr = ds.ExecuteSQL("SELECT GeomFromGPB(geom) FROM test") - f = sql_lyr.GetNextFeature() - if f.GetGeometryRef().ExportToWkt() != "POINT (2 49)": - f.DumpReadable() - pytest.fail() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT GeomFromGPB(geom) FROM test") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f.GetGeometryRef().ExportToWkt() == "POINT (2 49)" ds = None @@ -3289,17 +3156,16 @@ def test_ogr_gpkg_32(tmp_vsimem, tmp_path): ds = ogr.Open(fname) assert ds.GetLayerCount() == 1 - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_contents") - assert sql_lyr.GetFeatureCount() == 0 - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") - assert sql_lyr.GetFeatureCount() == 0 - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL("SELECT * FROM gpkg_contents") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( "SELECT * FROM sqlite_master WHERE name = 'gpkg_extensions'" - ) - assert sql_lyr.GetFeatureCount() == 0 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 ds = None assert validate(fname, tmpdir=tmp_path), "validation failed" @@ -3319,11 +3185,10 @@ def test_ogr_gpkg_33(tmp_vsimem): ds = None ds = ogr.Open(fname) - sql_lyr = ds.ExecuteSQL( - "SELECT * FROM gpkg_contents WHERE last_change = '2000-01-01T:00:00:00.000Z'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL( + "SELECT * FROM gpkg_contents WHERE last_change = '2000-01-01T:00:00:00.000Z'" + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 ds = None @@ -3591,13 +3456,11 @@ def test_ogr_gpkg_35(tmp_vsimem, tmp_path): # Try on read-only dataset ds = ogr.Open(dbname) - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_metadata WHERE id = 1") - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_metadata WHERE id = 1") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_metadata WHERE id = 2") - assert sql_lyr.GetFeatureCount() == 0 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_metadata WHERE id = 2") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 lyr = ds.GetLayerByName("test_nonspatial") assert lyr.GetMetadataItem("FOO") == "BAR" @@ -3655,11 +3518,10 @@ def test_ogr_gpkg_36(tmp_vsimem, tmp_path): ds.ExecuteSQL("CREATE INDEX my_idx ON test(foo)") # gpkg_data_columns should have been created because of AlternativeName set on field - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_data_columns WHERE table_name = 'test' AND column_name = 'foo'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 # Metadata lyr.SetMetadataItem("FOO", "BAR") @@ -3710,11 +3572,10 @@ def test_ogr_gpkg_36(tmp_vsimem, tmp_path): new_field_defn.SetAlternativeName("alt name") assert lyr.AlterFieldDefn(0, new_field_defn, ogr.ALTER_ALL_FLAG) == 0 - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_data_columns WHERE table_name = 'test' AND column_name = 'bar'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 # Violation of not-null constraint new_field_defn = ogr.FieldDefn("baz", ogr.OFTString) @@ -3733,11 +3594,10 @@ def test_ogr_gpkg_36(tmp_vsimem, tmp_path): pytest.fail() f = None - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_data_columns WHERE table_name = 'test' AND column_name = 'bar'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 # Just change the name, and run it outside an existing transaction lyr.StartTransaction() @@ -3745,11 +3605,10 @@ def test_ogr_gpkg_36(tmp_vsimem, tmp_path): assert lyr.AlterFieldDefn(0, new_field_defn, ogr.ALTER_NAME_FLAG) == 0 lyr.CommitTransaction() - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_data_columns WHERE table_name = 'test' AND column_name = 'baw2'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 lyr.ResetReading() f = lyr.GetNextFeature() @@ -3786,11 +3645,10 @@ def test_ogr_gpkg_36(tmp_vsimem, tmp_path): f = None # Check that index has been recreated - sql_lyr = ds.ExecuteSQL("SELECT * FROM sqlite_master WHERE name = 'my_idx'") - f = sql_lyr.GetNextFeature() - assert f is not None - f = None - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM sqlite_master WHERE name = 'my_idx'") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f is not None + f = None ds.ExecuteSQL("VACUUM") @@ -3801,17 +3659,15 @@ def test_ogr_gpkg_36(tmp_vsimem, tmp_path): # Try on read-only dataset ds = ogr.Open(dbname) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_data_columns WHERE table_name = 'test' AND column_name = 'baw'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_metadata_reference WHERE table_name = 'test' AND column_name = 'baw'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 lyr = ds.GetLayer(0) with gdal.quiet_errors(): @@ -3861,21 +3717,19 @@ def test_ogr_gpkg_36_alter_comment_after_alternative_name(tmp_vsimem, tmp_path): assert lyr.AlterFieldDefn(0, new_field_defn, ogr.ALTER_ALTERNATIVE_NAME_FLAG) == 0 # gpkg_data_columns should have been created because of AlternativeName set on field - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_data_columns WHERE table_name = 'test' AND column_name = 'foo'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 new_field_defn = ogr.FieldDefn(field.GetName(), field.GetType()) new_field_defn.SetComment("alt comment") assert lyr.AlterFieldDefn(0, new_field_defn, ogr.ALTER_COMMENT_FLAG) == 0 - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_data_columns WHERE table_name = 'test' AND column_name = 'foo'" - ) - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 assert lyr.GetLayerDefn().GetFieldDefn(0).GetAlternativeName() == "alt name" assert lyr.GetLayerDefn().GetFieldDefn(0).GetComment() == "alt comment" @@ -3958,11 +3812,10 @@ def test_ogr_gpkg_37(tmp_vsimem): pytest.fail() # Check that index has been recreated - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM sqlite_master WHERE name = 'my_idx_foo' OR name = 'my_idx_bar'" - ) - assert sql_lyr.GetFeatureCount() == 2 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 2 ds = None @@ -4004,12 +3857,14 @@ def test_ogr_gpkg_38(tmp_vsimem, spatial_index): # Test that we can compute the extent of a layer that has none registered in gpkg_contents extent = lyr.GetExtent(force=1) assert extent == (1, 3, 2, 4) - sql_lyr = ds.ExecuteSQL("SELECT min_x, min_y, max_x, max_y FROM gpkg_contents") - f = sql_lyr.GetNextFeature() - if f["min_x"] != 1 or f["min_y"] != 2 or f["max_x"] != 3 or f["max_y"] != 4: - f.DumpReadable() - pytest.fail() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL( + "SELECT min_x, min_y, max_x, max_y FROM gpkg_contents" + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + if f["min_x"] != 1 or f["min_y"] != 2 or f["max_x"] != 3 or f["max_y"] != 4: + f.DumpReadable() + pytest.fail() + extent = lyr.GetExtent(force=0) assert extent == (1, 3, 2, 4) @@ -4106,17 +3961,17 @@ def test_ogr_gpkg_40(tmp_vsimem, tmp_path): ds = ogr.Open(dbname) assert ds.GetLayerCount() == 1 - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_contents") - assert sql_lyr.GetFeatureCount() == 1 - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") - assert sql_lyr.GetFeatureCount() == 0 - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL("SELECT * FROM gpkg_contents") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 1 + + with ds.ExecuteSQL("SELECT * FROM gpkg_geometry_columns") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + + with ds.ExecuteSQL( "SELECT * FROM sqlite_master WHERE name = 'gpkg_extensions'" - ) - assert sql_lyr.GetFeatureCount() == 0 - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + assert sql_lyr.GetFeatureCount() == 0 + ds = None assert validate(dbname, tmpdir=tmp_path), "validation failed" @@ -4162,25 +4017,21 @@ def test_ogr_gpkg_41(tmp_vsimem): def foo_has_trigger(ds): - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT COUNT(*) FROM sqlite_master WHERE name = 'trigger_insert_feature_count_foo'", dialect="DEBUG", - ) - f = sql_lyr.GetNextFeature() - has_trigger = f.GetField(0) == 1 - f = None - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + has_trigger = f.GetField(0) == 1 return has_trigger def get_feature_count_from_gpkg_contents(ds): - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT feature_count FROM gpkg_ogr_contents", dialect="DEBUG" - ) - f = sql_lyr.GetNextFeature() - val = f.GetField(0) - f = None - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + val = f.GetField(0) return val @@ -4284,24 +4135,22 @@ def test_ogr_gpkg_42(tmp_vsimem): ds = None ds = ogr.Open(dbname, update=1) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT feature_count FROM gpkg_ogr_contents WHERE table_name = 'bar'", dialect="DEBUG", - ) - f = sql_lyr.GetNextFeature() - val = f.GetField(0) - f = None - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + val = f.GetField(0) + f = None assert val == 5000 # Test layer deletion ds.DeleteLayer(0) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT feature_count FROM gpkg_ogr_contents", dialect="DEBUG" - ) - f = sql_lyr.GetNextFeature() - assert f is None - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f is None ds = None # Test without feature_count column @@ -4319,9 +4168,8 @@ def test_ogr_gpkg_42(tmp_vsimem): ds = ogr.Open(dbname, update=1) # Check that feature_count column is missing - sql_lyr = ds.ExecuteSQL("PRAGMA table_info(gpkg_contents)") - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("PRAGMA table_info(gpkg_contents)") as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 10 assert not foo_has_trigger(ds) @@ -4573,22 +4421,23 @@ def test_ogr_gpkg_46(tmp_vsimem): # Operations not valid on a view with gdal.quiet_errors(): - ds.ReleaseResultSet( - ds.ExecuteSQL("SELECT CreateSpatialIndex('my_view', 'my_geom')") - ) - ds.ReleaseResultSet( - ds.ExecuteSQL("SELECT DisableSpatialIndex('my_view', 'my_geom')") - ) + with ds.ExecuteSQL( + "SELECT CreateSpatialIndex('my_view', 'my_geom')" + ) as sql_lyr: + pass + with ds.ExecuteSQL( + "SELECT DisableSpatialIndex('my_view', 'my_geom')" + ) as sql_lyr: + pass lyr.AlterFieldDefn(0, lyr.GetLayerDefn().GetFieldDefn(0), ogr.ALTER_ALL_FLAG) lyr.DeleteField(0) lyr.ReorderFields([0]) lyr.CreateField(ogr.FieldDefn("bar")) # Check if spatial index is recognized - sql_lyr = ds.ExecuteSQL("SELECT HasSpatialIndex('my_view', 'my_geom')") - f = sql_lyr.GetNextFeature() - has_spatial_index = f.GetField(0) == 1 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT HasSpatialIndex('my_view', 'my_geom')") as sql_lyr: + f = sql_lyr.GetNextFeature() + has_spatial_index = f.GetField(0) == 1 if not has_spatial_index: ds = None pytest.skip("SQLite likely built without SQLITE_HAS_COLUMN_METADATA") @@ -5089,15 +4938,14 @@ def test_ogr_gpkg_56(tmp_vsimem): ds = gdal.VectorTranslate( tmp_vsimem / "ogr_gpkg_56.gpkg", "data/poly.shp", format="GPKG" ) - lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "select a.fid as fid1, b.fid as fid2 from poly a, poly b order by fid1, fid2" - ) - lyr.GetNextFeature() - f = lyr.GetNextFeature() - if f.GetField("fid1") != 1 or f.GetField("fid2") != 2: - f.DumpReadable() - pytest.fail() - ds.ReleaseResultSet(lyr) + ) as lyr: + lyr.GetNextFeature() + f = lyr.GetNextFeature() + if f.GetField("fid1") != 1 or f.GetField("fid2") != 2: + f.DumpReadable() + pytest.fail() ds = None @@ -5312,10 +5160,9 @@ def test_ogr_gpkg_58(tmp_vsimem): ) ds = ogr.Open(out_filename) - sql_lyr = ds.ExecuteSQL("SELECT HasSpatialIndex('poly', 'geom')") - f = sql_lyr.GetNextFeature() - assert f.GetField(0) == 1 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT HasSpatialIndex('poly', 'geom')") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f.GetField(0) == 1 ds = None gdal.Unlink(out_filename) @@ -5336,10 +5183,9 @@ def test_ogr_gpkg_59(tmp_vsimem): ) ds = ogr.Open(out_filename, update=1) - sql_lyr = ds.ExecuteSQL("SELECT CreateSpatialIndex('poly', 'geom')") - f = sql_lyr.GetNextFeature() - assert f.GetField(0) == 1 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT CreateSpatialIndex('poly', 'geom')") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f.GetField(0) == 1 ds = None @@ -5419,10 +5265,9 @@ def test_ogr_gpkg_wal(tmp_path): def test_ogr_gpkg_nolock(tmp_path): def get_nolock(ds): - sql_lyr = ds.ExecuteSQL("SELECT nolock", dialect="DEBUG") - f = sql_lyr.GetNextFeature() - res = True if f[0] == 1 else False - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT nolock", dialect="DEBUG") as sql_lyr: + f = sql_lyr.GetNextFeature() + res = True if f[0] == 1 else False return res # needs to be a real file @@ -5466,7 +5311,8 @@ def get_nolock(ds): # Now turn on WAL ds = ogr.Open(filename, update=1) - ds.ReleaseResultSet(ds.ExecuteSQL("PRAGMA journal_mode = WAL")) + with ds.ExecuteSQL("PRAGMA journal_mode = WAL") as sql_lyr: + assert sql_lyr ds = None # Lockless mode should NOT be honored by GDAL on a WAL enabled file @@ -5501,10 +5347,9 @@ def test_ogr_gpkg_test_ogrsf(gpkg_ds): # Do integrity check first gpkg_ds = ogr.Open(dbname) - sql_lyr = gpkg_ds.ExecuteSQL("PRAGMA integrity_check") - feat = sql_lyr.GetNextFeature() - assert feat.GetField(0) == "ok", "integrity check failed" - gpkg_ds.ReleaseResultSet(sql_lyr) + with gpkg_ds.ExecuteSQL("PRAGMA integrity_check") as sql_lyr: + feat = sql_lyr.GetNextFeature() + assert feat.GetField(0) == "ok", "integrity check failed" import test_cli_utilities @@ -5545,7 +5390,8 @@ def test_ogr_gpkg_json(tmp_vsimem, tmp_path): lyr.CreateField(fld_defn) assert lyr.GetLayerDefn().GetFieldDefn(0).GetSubType() == ogr.OFSTJSON - ds.ReleaseResultSet(ds.ExecuteSQL("SELECT 1 FROM test")) # will crystalize + with ds.ExecuteSQL("SELECT 1 FROM test") as sql_lyr: # will crystalize + pass fld_defn = ogr.FieldDefn("test2_json", ogr.OFTString) fld_defn.SetSubType(ogr.OFSTJSON) @@ -5605,9 +5451,10 @@ def test_ogr_gpkg_json(tmp_vsimem, tmp_path): == ogr.OFSTJSON ) - sql_lyr = ds.ExecuteSQL("SELECT 1 FROM gpkg_data_columns WHERE table_name = 'test'") - fc = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL( + "SELECT 1 FROM gpkg_data_columns WHERE table_name = 'test'" + ) as sql_lyr: + fc = sql_lyr.GetFeatureCount() assert fc == 1 ds = None @@ -5719,10 +5566,9 @@ def test_ogr_gpkg_mixed_dimensionality_unknown_layer_geometry_type( lyr = ds.GetLayer(0) assert lyr.GetGeomType() == ogr.wkbUnknown - sql_lyr = ds.ExecuteSQL("SELECT z FROM gpkg_geometry_columns") - f = sql_lyr.GetNextFeature() - assert f.GetField(0) == 2 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT z FROM gpkg_geometry_columns") as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f.GetField(0) == 2 ds = None @@ -5789,29 +5635,26 @@ def test_ogr_gpkg_fixup_wrong_rtree_trigger(tmp_vsimem): # Open in read-only mode ds = ogr.Open(filename) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND name = 'rtree_test-with-dash_geometry_update3'" - ) - f = sql_lyr.GetNextFeature() - sql = f["sql"] - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + sql = f["sql"] ds = None assert sql == wrong_trigger # Open in update mode ds = ogr.Open(filename, update=1) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND name = 'rtree_test-with-dash_geometry_update3'" - ) - f = sql_lyr.GetNextFeature() - sql = f["sql"] - ds.ReleaseResultSet(sql_lyr) - sql_lyr = ds.ExecuteSQL( + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + sql = f["sql"] + with ds.ExecuteSQL( "SELECT sql FROM sqlite_master WHERE type = 'trigger' AND name = 'rtree_test2_geometry_update3'" - ) - f = sql_lyr.GetNextFeature() - sql2 = f["sql"] - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + sql2 = f["sql"] ds = None gdal.Unlink(filename) @@ -5838,9 +5681,8 @@ def test_ogr_gpkg_prelude_statements(tmp_vsimem): f"PRELUDE_STATEMENTS=ATTACH DATABASE '{tmp_vsimem}/test.gpkg' AS other" ], ) - sql_lyr = ds.ExecuteSQL("SELECT * FROM poly JOIN other.poly USING (eas_id)") - assert sql_lyr.GetFeatureCount() == 10 - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM poly JOIN other.poly USING (eas_id)") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 10 ############################################################################### @@ -5872,11 +5714,10 @@ def test_ogr_gpkg_datetime_timezones(tmp_vsimem): f = lyr.GetNextFeature() assert f.GetField("dt") == "2019/12/31 23:34:56.789+00" - sql_lyr = ds.ExecuteSQL("SELECT dt || '' FROM test") - f = sql_lyr.GetNextFeature() - # check that milliseconds are written to be strictly compliant with the GPKG spec - assert f.GetField(0) == "2020-01-01T01:34:56.000Z" - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT dt || '' FROM test") as sql_lyr: + f = sql_lyr.GetNextFeature() + # check that milliseconds are written to be strictly compliant with the GPKG spec + assert f.GetField(0) == "2020-01-01T01:34:56.000Z" ds = None @@ -5909,7 +5750,7 @@ def abortAfterDelay(): SELECT i FROM r WHERE i = 1;""" with gdal.quiet_errors(): - ds.ExecuteSQL(sql) + assert ds.ExecuteSQL(sql) is None end = time.time() assert int(end - start) < 2 @@ -5928,7 +5769,7 @@ def abortAfterDelay2(): # Long running query with gdal.quiet_errors(): - ds2.ExecuteSQL(sql) + assert ds2.ExecuteSQL(sql) is None end = time.time() assert int(end - start) < 2 @@ -5956,15 +5797,14 @@ def test_ogr_gpkg_st_transform_no_record_spatial_ref_sys(tmp_vsimem): ds = None pytest.skip("Spatialite missing or too old") - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT ST_Transform(SetSRID(geom, 32631), 32731) FROM test" - ) - # Fails on a number of configs - # assert sql_lyr.GetSpatialRef().GetAuthorityCode(None) == '32731' - f = sql_lyr.GetNextFeature() - assert f.GetGeometryRef().ExportToWkt() == "POINT (500000 10000000)" - f = None - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + # Fails on a number of configs + # assert sql_lyr.GetSpatialRef().GetAuthorityCode(None) == '32731' + f = sql_lyr.GetNextFeature() + assert f.GetGeometryRef().ExportToWkt() == "POINT (500000 10000000)" + f = None ds = None @@ -5975,12 +5815,11 @@ def test_ogr_gpkg_st_transform_no_record_spatial_ref_sys(tmp_vsimem): def test_ogr_gpkg_deferred_spi_creation(tmp_vsimem): def has_spi(ds): - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT 1 FROM sqlite_master WHERE name = 'rtree_test_geom'", dialect="DEBUG", - ) - res = sql_lyr.GetNextFeature() is not None - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + res = sql_lyr.GetNextFeature() is not None return res ds = ogr.GetDriverByName("GPKG").CreateDataSource(tmp_vsimem / "test.gpkg") @@ -6008,7 +5847,8 @@ def has_spi(ds): assert lyr.DeleteField(0) == ogr.OGRERR_NONE assert not has_spi(ds) - ds.ReleaseResultSet(ds.ExecuteSQL("SELECT 1")) + with ds.ExecuteSQL("SELECT 1") as sql_lyr: + assert sql_lyr assert has_spi(ds) # GetNextFeature() with spatial filter should cause SPI creation @@ -6039,12 +5879,11 @@ def has_spi(ds): @pytest.mark.parametrize("gpkg_version", ["1.2", "1.4"]) def test_ogr_gpkg_deferred_spi_update(tmp_vsimem, gpkg_version): def has_spi_triggers(ds): - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM sqlite_master WHERE type = 'trigger' AND name LIKE 'rtree_test_geom%'", dialect="DEBUG", - ) - res = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + res = sql_lyr.GetFeatureCount() return res == 6 or res == 7 filename = tmp_vsimem / "test.gpkg" @@ -6076,17 +5915,15 @@ def has_spi_triggers(ds): lyr.CreateFeature(f) assert not has_spi_triggers(ds) - sql_lyr = ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") - res = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") as sql_lyr: + res = sql_lyr.GetFeatureCount() assert res == 2 ds.CommitTransaction() assert has_spi_triggers(ds) - sql_lyr = ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") - res = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") as sql_lyr: + res = sql_lyr.GetFeatureCount() assert res == 3 ds = None @@ -6113,17 +5950,15 @@ def has_spi_triggers(ds): lyr.CreateFeature(f) assert not has_spi_triggers(ds) - sql_lyr = ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") - res = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") as sql_lyr: + res = sql_lyr.GetFeatureCount() assert res == 1 ds.RollbackTransaction() assert has_spi_triggers(ds) - sql_lyr = ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") - res = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM rtree_test_geom", dialect="DEBUG") as sql_lyr: + res = sql_lyr.GetFeatureCount() assert res == 0 ds = None @@ -6387,9 +6222,8 @@ def test_ogr_gpkg_field_domains(tmp_vsimem, tmp_path): # Test read support ds = gdal.OpenEx(filename, gdal.OF_VECTOR) - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_data_column_constraints") - assert sql_lyr is not None - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT * FROM gpkg_data_column_constraints") as sql_lyr: + assert sql_lyr assert set(ds.GetFieldDomainNames()) == { "enum_domain", @@ -6503,11 +6337,11 @@ def test_ogr_gpkg_field_domains(tmp_vsimem, tmp_path): assert fld_defn.GetDomainName() == "range_domain_int" if gdal.GetDriverByName("GPKG").GetMetadataItem("SQLITE_HAS_COLUMN_METADATA"): - sql_lyr = ds.ExecuteSQL("SELECT with_range_domain_int FROM test") - assert ( - sql_lyr.GetLayerDefn().GetFieldDefn(0).GetDomainName() == "range_domain_int" - ) - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL("SELECT with_range_domain_int FROM test") as sql_lyr: + assert ( + sql_lyr.GetLayerDefn().GetFieldDefn(0).GetDomainName() + == "range_domain_int" + ) ds = None @@ -6925,13 +6759,12 @@ def test_ogr_gpkg_fixup_wrong_mr_column_name_update_trigger(tmp_vsimem): # Open in update mode ds = ogr.Open(filename, update=1) - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT sql FROM sqlite_master WHERE type = 'trigger' " + "AND name = 'gpkg_metadata_reference_column_name_update'" - ) - f = sql_lyr.GetNextFeature() - sql = f["sql"] - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + sql = f["sql"] ds = None assert "column_nameIS" not in sql @@ -6972,47 +6805,46 @@ def test_ogr_gpkg_crs_coordinate_epoch(tmp_vsimem, tmp_path): ds = ogr.Open(filename) - sql_lyr = ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys ORDER BY srs_id") - assert sql_lyr.GetFeatureCount() == 7 + with ds.ExecuteSQL("SELECT * FROM gpkg_spatial_ref_sys ORDER BY srs_id") as sql_lyr: + assert sql_lyr.GetFeatureCount() == 7 - sql_lyr.GetNextFeature() - sql_lyr.GetNextFeature() + sql_lyr.GetNextFeature() + sql_lyr.GetNextFeature() - f = sql_lyr.GetNextFeature() - assert f - assert f["srs_id"] == 4258 - assert f["organization"] == "EPSG" - assert f["organization_coordsys_id"] == 4258 - assert f["epoch"] is None + f = sql_lyr.GetNextFeature() + assert f + assert f["srs_id"] == 4258 + assert f["organization"] == "EPSG" + assert f["organization_coordsys_id"] == 4258 + assert f["epoch"] is None - f = sql_lyr.GetNextFeature() - assert f - assert f["srs_id"] == 4326 - assert f["organization"] == "EPSG" - assert f["organization_coordsys_id"] == 4326 - assert f["epoch"] is None + f = sql_lyr.GetNextFeature() + assert f + assert f["srs_id"] == 4326 + assert f["organization"] == "EPSG" + assert f["organization_coordsys_id"] == 4326 + assert f["epoch"] is None - f = sql_lyr.GetNextFeature() - assert f - assert f["srs_id"] == 100000 - assert f["organization"] == "NONE" - assert f["organization_coordsys_id"] == 100000 - assert f["epoch"] == 2021.3 + f = sql_lyr.GetNextFeature() + assert f + assert f["srs_id"] == 100000 + assert f["organization"] == "NONE" + assert f["organization_coordsys_id"] == 100000 + assert f["epoch"] == 2021.3 - f = sql_lyr.GetNextFeature() - assert f - assert f["srs_id"] == 100001 - assert f["organization"] == "EPSG" - assert f["organization_coordsys_id"] == 7665 - assert f["epoch"] == 2021.3 + f = sql_lyr.GetNextFeature() + assert f + assert f["srs_id"] == 100001 + assert f["organization"] == "EPSG" + assert f["organization_coordsys_id"] == 7665 + assert f["epoch"] == 2021.3 - f = sql_lyr.GetNextFeature() - assert f - assert f["srs_id"] == 100002 - assert f["organization"] == "EPSG" - assert f["organization_coordsys_id"] == 7665 - assert f["epoch"] == 2021.2 - ds.ReleaseResultSet(sql_lyr) + f = sql_lyr.GetNextFeature() + assert f + assert f["srs_id"] == 100002 + assert f["organization"] == "EPSG" + assert f["organization_coordsys_id"] == 7665 + assert f["epoch"] == 2021.2 lyr = ds.GetLayerByName("lyr_with_coordinate_epoch_unknown_srs") srs = lyr.GetSpatialRef() @@ -7184,12 +7016,11 @@ def test_ogr_gpkg_relations(tmp_vsimem, tmp_path): ds = gdal.OpenEx(filename, gdal.OF_VECTOR | gdal.OF_UPDATE) ds.ExecuteSQL("DELLAYER:a_renamed") - sql_lyr = ds.ExecuteSQL( + with ds.ExecuteSQL( "SELECT * FROM gpkg_extensions WHERE extension_name IN ('related_tables', 'gpkg_related_tables')" - ) - f = sql_lyr.GetNextFeature() - assert f is None - ds.ReleaseResultSet(sql_lyr) + ) as sql_lyr: + f = sql_lyr.GetNextFeature() + assert f is None assert ds.GetRelationshipNames() is None ds = None @@ -7325,9 +7156,8 @@ def clone_relationship(relationship): ds = gdaltest.gpkg_dr.CreateDataSource(filename) def get_query_row_count(query): - sql_lyr = ds.ExecuteSQL(query) - res = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL(query) as sql_lyr: + res = sql_lyr.GetFeatureCount() return res relationship = gdal.Relationship( @@ -7845,9 +7675,8 @@ def test_ogr_gpkg_add_relationship_complex_names(tmp_vsimem, tmp_path): ds = gdaltest.gpkg_dr.CreateDataSource(filename) def get_query_row_count(query): - sql_lyr = ds.ExecuteSQL(query) - res = sql_lyr.GetFeatureCount() - ds.ReleaseResultSet(sql_lyr) + with ds.ExecuteSQL(query) as sql_lyr: + res = sql_lyr.GetFeatureCount() return res relationship = gdal.Relationship( @@ -8614,7 +8443,8 @@ def test_ogr_gpkg_immutable(tmp_path): tmp_path / "read_only_test_ogr_gpkg_immutable/test.gpkg" ) ds.CreateLayer("foo") - ds.ExecuteSQL("PRAGMA journal_mode = WAL") + with ds.ExecuteSQL("PRAGMA journal_mode = WAL") as sql_lyr: + assert sql_lyr ds = None # Turn directory in read-only mode @@ -10182,12 +10012,12 @@ def test_ogr_gpkg_like_utf8(tmp_vsimem): ) as sql_lyr: assert sql_lyr.GetFeatureCount() == 0 - ds.ExecuteSQL("PRAGMA case_sensitive_like = 1") + assert ds.ExecuteSQL("PRAGMA case_sensitive_like = 1") is None with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: assert sql_lyr.GetFeatureCount() == 0 - ds.ExecuteSQL("PRAGMA case_sensitive_like = 0") + assert ds.ExecuteSQL("PRAGMA case_sensitive_like = 0") is None with ds.ExecuteSQL("SELECT * FROM test WHERE 'e' LIKE 'E'") as sql_lyr: assert sql_lyr.GetFeatureCount() == 1 @@ -10664,3 +10494,49 @@ def test_gpkg_rename_hidden_table(tmp_vsimem): gdal.VSIFCloseL(f) assert "hidden_foo_table" not in content + + +############################################################################### +# Test creating duplicate field names + + +@gdaltest.enable_exceptions() +def test_gpkg_create_duplicate_field_names(tmp_vsimem): + + filename = str(tmp_vsimem / "test_gpkg_create_duplicate_field_names.gpkg") + with ogr.GetDriverByName("GPKG").CreateDataSource(filename) as ds: + lyr = ds.CreateLayer("test") + lyr.CreateField(ogr.FieldDefn("foo")) + with pytest.raises( + Exception, match="A field with the same name already exists" + ): + lyr.CreateField(ogr.FieldDefn("foo")) + assert lyr.GetLayerDefn().GetFieldCount() == 1 + with pytest.raises( + Exception, match="A field with the same name already exists" + ): + lyr.CreateField(ogr.FieldDefn("FOO")) + assert lyr.GetLayerDefn().GetFieldCount() == 1 + with pytest.raises( + Exception, match="It has the same name as the geometry field" + ): + lyr.CreateField(ogr.FieldDefn("geom")) + assert lyr.GetLayerDefn().GetFieldCount() == 1 + + +############################################################################### +# Test creating more than 2000 fields + + +@gdaltest.enable_exceptions() +def test_gpkg_create_more_than_2000_fields(tmp_vsimem): + + filename = str(tmp_vsimem / "test_gpkg_create_more_than_2000_fields.gpkg") + with ogr.GetDriverByName("GPKG").CreateDataSource(filename) as ds: + lyr = ds.CreateLayer("test") + + for i in range(2000 - 2): + lyr.CreateField(ogr.FieldDefn(f"foo{i}")) + with pytest.raises(Exception, match="Limit of 2000 columns reached"): + lyr.CreateField(ogr.FieldDefn("foo")) + assert lyr.GetLayerDefn().GetFieldCount() == 2000 - 2 diff --git a/autotest/ogr/ogr_oci.py b/autotest/ogr/ogr_oci.py index 7952d947633a..02e3683cbea4 100755 --- a/autotest/ogr/ogr_oci.py +++ b/autotest/ogr/ogr_oci.py @@ -54,7 +54,10 @@ def setup_tests(): gdaltest.oci_ds.ExecuteSQL("DELLAYER:xpoly") gdaltest.oci_ds.ExecuteSQL("DELLAYER:testsrs") gdaltest.oci_ds.ExecuteSQL("DELLAYER:testsrs2") - gdaltest.oci_ds.ExecuteSQL("drop table geom_test") + try: + gdaltest.oci_ds.ExecuteSQL("drop table geom_test") + except Exception: + pass gdaltest.oci_ds.ExecuteSQL("DELLAYER:test_POINT") gdaltest.oci_ds.ExecuteSQL("DELLAYER:test_POINT3") gdaltest.oci_ds.ExecuteSQL("DELLAYER:test_LINESTRING") @@ -730,16 +733,21 @@ def test_ogr_oci_19(): feat.SetField("MYDATE", "2015/02/03") feat.SetField("MYDATETIME", "2015/02/03 11:33:44") lyr.CreateFeature(feat) + feat = ogr.Feature(lyr.GetLayerDefn()) + feat.SetField("MYDATETIME", "2015/02/03 11:33:44.12345") + lyr.CreateFeature(feat) lyr.SyncToDisk() - sql_lyr = gdaltest.oci_ds.ExecuteSQL("SELECT MYDATE, MYDATETIME FROM testdate") - assert sql_lyr.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTDate - assert sql_lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTDateTime - f = sql_lyr.GetNextFeature() - if f.GetField(0) != "2015/02/03" or f.GetField(1) != "2015/02/03 11:33:44": - f.DumpReadable() - pytest.fail() - gdaltest.oci_ds.ReleaseResultSet(sql_lyr) + with gdaltest.oci_ds.ExecuteSQL( + "SELECT MYDATE, MYDATETIME FROM testdate" + ) as sql_lyr: + assert sql_lyr.GetLayerDefn().GetFieldDefn(0).GetType() == ogr.OFTDate + assert sql_lyr.GetLayerDefn().GetFieldDefn(1).GetType() == ogr.OFTDateTime + f = sql_lyr.GetNextFeature() + assert f.GetField(0) == "2015/02/03" + assert f.GetField(1) == "2015/02/03 11:33:44" + f = sql_lyr.GetNextFeature() + assert f.GetField(1) == "2015/02/03 11:33:44.123" ############################################################################### diff --git a/autotest/ogr/ogr_openfilegdb_write.py b/autotest/ogr/ogr_openfilegdb_write.py index 2cd1db7dc95b..ad2ccf036bb6 100755 --- a/autotest/ogr/ogr_openfilegdb_write.py +++ b/autotest/ogr/ogr_openfilegdb_write.py @@ -4265,7 +4265,7 @@ def test_ogr_openfilegdb_write_new_datetime_types(tmp_vsimem): lyr = ds.GetLayerByName("date_types") lyr_defn = lyr.GetLayerDefn() - fld_defn = lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("date_")) + fld_defn = lyr_defn.GetFieldDefn(lyr_defn.GetFieldIndex("date")) assert fld_defn.GetType() == ogr.OFTDateTime assert fld_defn.GetDefault() == "'2023/02/01 04:05:06'" @@ -4282,13 +4282,13 @@ def test_ogr_openfilegdb_write_new_datetime_types(tmp_vsimem): assert fld_defn.GetDefault() == "'2023/02/01 04:05:06.000+06:00'" f = lyr.GetNextFeature() - assert f["date_"] == "2023/11/29 13:14:15+00" + assert f["date"] == "2023/11/29 13:14:15+00" assert f["date_only"] == "2023/11/29" assert f["time_only"] == "13:14:15" assert f["timestamp_offset"] == "2023/11/29 13:14:15-05" f = lyr.GetNextFeature() - assert f["date_"] == "2023/12/31 00:01:01+00" + assert f["date"] == "2023/12/31 00:01:01+00" assert f["date_only"] == "2023/12/31" assert f["time_only"] == "00:01:01" assert f["timestamp_offset"] == "2023/12/31 00:01:01+10" @@ -4297,13 +4297,13 @@ def test_ogr_openfilegdb_write_new_datetime_types(tmp_vsimem): lyr_defn = lyr.GetLayerDefn() f = lyr.GetNextFeature() - assert f["date_"] == "2023/11/29 13:14:15.678+00" + assert f["date"] == "2023/11/29 13:14:15.678+00" assert f["date_only"] == "2023/11/29" assert f["time_only"] == "13:14:15" assert f["timestamp_offset"] == "2023/11/29 13:14:15-05" f = lyr.GetNextFeature() - assert f["date_"] == "2023/12/31 00:01:01.001+00" + assert f["date"] == "2023/12/31 00:01:01.001+00" assert f["date_only"] == "2023/12/31" assert f["time_only"] == "00:01:01" assert f["timestamp_offset"] == "2023/12/31 00:01:01+10" diff --git a/autotest/ogr/ogr_pdf.py b/autotest/ogr/ogr_pdf.py index 1fa585a61a7c..9be31d4db7bd 100755 --- a/autotest/ogr/ogr_pdf.py +++ b/autotest/ogr/ogr_pdf.py @@ -38,7 +38,7 @@ def has_read_support(): return True -@pytest.fixture(params=("DEFAULT", "PODOFO")) +@pytest.fixture(params=("DEFAULT", "PDFIUM", "POPPLER", "PODOFO")) def pdf_lib(request): lib_name = request.param @@ -438,8 +438,10 @@ def test_ogr_pdf_arcgis_12_9(): force_download="CI" in os.environ, ) - ds = ogr.Open("tmp/cache/9130-3N+PARRAMATTA+RIVER.pdf") - assert ds.GetLayerCount() == 58 + # OGR_ORGANIZE_POLYGONS=SKIP to make the test as fast as possible + with gdaltest.config_option("OGR_ORGANIZE_POLYGONS", "SKIP"): + ds = ogr.Open("tmp/cache/9130-3N+PARRAMATTA+RIVER.pdf") + assert ds.GetLayerCount() == 66 lyr = ds.GetLayer("Background") background_extent = lyr.GetExtent() assert background_extent == pytest.approx( @@ -452,3 +454,23 @@ def test_ogr_pdf_arcgis_12_9(): (314770.38136460556, 338159.3346125973, 6249907.04700745, 6264139.920707164), rel=1e-5, ) + + +############################################################################### +# Test bugfix for https://github.com/OSGeo/gdal/issues/11034 + + +@pytest.mark.skipif(not has_read_support(), reason="PDF driver lacks read support") +def test_ogr_pdf_recursive_resources_and_oc_name_and_empty_ocg_name(pdf_lib): + + with ogr.Open( + "data/pdf/recursive_resources_and_oc_name_and_empty_ocg_name.pdf" + ) as ds: + assert ds.GetLayerCount() == 1 + lyr = ds.GetLayer("unnamed") + assert lyr.GetFeatureCount() == 4 + feat = lyr.GetNextFeature() + expected_wkt = """LINESTRING (210719.044079199 5340565.58196092,210703.077661677 5340510.89851925)""" + ogrtest.check_feature_geometry( + feat, ogr.CreateGeometryFromWkt(expected_wkt), max_error=1 + ) diff --git a/autotest/ogr/ogr_xodr.py b/autotest/ogr/ogr_xodr.py index 3432ce2dc4d2..fc823cd6f9cb 100644 --- a/autotest/ogr/ogr_xodr.py +++ b/autotest/ogr/ogr_xodr.py @@ -21,6 +21,19 @@ xodr_file = "data/xodr/5g_living_lab_A39_Wolfsburg-West.xodr" +def test_ogr_xodr_i_do_not_exist(): + + assert gdal.IdentifyDriverEx("i_do_not_exist.xodr") is None + + +def test_ogr_xodr_empty(): + + gdal.ErrorReset() + with ogr.Open("data/xodr/empty.xodr") as ds: + assert gdal.GetLastErrorMsg() == "" + assert ds.GetLayerCount() == 6 + + def test_ogr_xodr_test_ogrsf(): import test_cli_utilities diff --git a/autotest/utilities/test_gdal_contour.py b/autotest/utilities/test_gdal_contour.py index 5f1891795b3c..c46f277afa2e 100755 --- a/autotest/utilities/test_gdal_contour.py +++ b/autotest/utilities/test_gdal_contour.py @@ -112,34 +112,33 @@ def test_gdal_contour_1(gdal_contour_path, testdata_tif, tmp_path): ] expected_height = [0, 10, 20] - lyr = ds.ExecuteSQL("select * from contour order by elev asc") + with ds.ExecuteSQL("select * from contour order by elev asc") as lyr: - raster_srs_wkt = gdal.Open(testdata_tif).GetSpatialRef().ExportToWkt() + raster_srs_wkt = gdal.Open(testdata_tif).GetSpatialRef().ExportToWkt() - assert ( - lyr.GetSpatialRef().ExportToWkt() == raster_srs_wkt - ), "Did not get expected spatial ref" - - assert lyr.GetFeatureCount() == len(expected_envelopes) + assert ( + lyr.GetSpatialRef().ExportToWkt() == raster_srs_wkt + ), "Did not get expected spatial ref" - size = 160 - precision = 1.0 / size + assert lyr.GetFeatureCount() == len(expected_envelopes) - i = 0 - for feat in lyr: - geom = feat.GetGeometryRef() - envelope = geom.GetEnvelope() - assert feat.GetField("elev") == expected_height[i] - for j in range(4): - if expected_envelopes[i][j] != pytest.approx(envelope[j], rel=1e-8): - print("i=%d, wkt=%s" % (i, geom.ExportToWkt())) - print(geom.GetEnvelope()) - pytest.fail( - "%f, %f" % (expected_envelopes[i][j] - envelope[j], precision / 2) - ) - i = i + 1 + size = 160 + precision = 1.0 / size - ds.ReleaseResultSet(lyr) + i = 0 + for feat in lyr: + geom = feat.GetGeometryRef() + envelope = geom.GetEnvelope() + assert feat.GetField("elev") == expected_height[i] + for j in range(4): + if expected_envelopes[i][j] != pytest.approx(envelope[j], rel=1e-8): + print("i=%d, wkt=%s" % (i, geom.ExportToWkt())) + print(geom.GetEnvelope()) + pytest.fail( + "%f, %f" + % (expected_envelopes[i][j] - envelope[j], precision / 2) + ) + i = i + 1 ############################################################################### @@ -173,29 +172,28 @@ def test_gdal_contour_2(gdal_contour_path, testdata_tif, tmp_path): ] expected_height = [10, 20, 25] - lyr = ds.ExecuteSQL("select * from contour order by elev asc") - - assert lyr.GetFeatureCount() == len(expected_envelopes) - - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetGeometryRef().GetZ(0) == expected_height[i] - envelope = feat.GetGeometryRef().GetEnvelope() - assert feat.GetField("elev") == expected_height[i] - for j in range(4): - if expected_envelopes[i][j] != pytest.approx( - envelope[j], abs=precision / 2 * 1.001 - ): - print("i=%d, wkt=%s" % (i, feat.GetGeometryRef().ExportToWkt())) - print(feat.GetGeometryRef().GetEnvelope()) - pytest.fail( - "%f, %f" % (expected_envelopes[i][j] - envelope[j], precision / 2) - ) - i = i + 1 - feat = lyr.GetNextFeature() + with ds.ExecuteSQL("select * from contour order by elev asc") as lyr: - ds.ReleaseResultSet(lyr) + assert lyr.GetFeatureCount() == len(expected_envelopes) + + i = 0 + feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetGeometryRef().GetZ(0) == expected_height[i] + envelope = feat.GetGeometryRef().GetEnvelope() + assert feat.GetField("elev") == expected_height[i] + for j in range(4): + if expected_envelopes[i][j] != pytest.approx( + envelope[j], abs=precision / 2 * 1.001 + ): + print("i=%d, wkt=%s" % (i, feat.GetGeometryRef().ExportToWkt())) + print(feat.GetGeometryRef().GetEnvelope()) + pytest.fail( + "%f, %f" + % (expected_envelopes[i][j] - envelope[j], precision / 2) + ) + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -213,19 +211,17 @@ def test_gdal_contour_3(gdal_contour_path, tmp_path): ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select distinct elev from contour order by elev asc") + with ds.ExecuteSQL("select distinct elev from contour order by elev asc") as lyr: - expected_heights = [100, 150, 200, 250, 300, 350, 400, 450] - assert lyr.GetFeatureCount() == len(expected_heights) + expected_heights = [100, 150, 200, 250, 300, 350, 400, 450] + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - i = i + 1 + i = 0 feat = lyr.GetNextFeature() - - ds.ReleaseResultSet(lyr) + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -402,18 +398,18 @@ def test_gdal_contour_fl_and_i(gdal_contour_path, testdata_tif, tmp_path): ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select elev from contour order by elev asc") + with ds.ExecuteSQL("select elev from contour order by elev asc") as lyr: - expected_heights = [0, 6, 10, 16, 20] + expected_heights = [0, 6, 10, 16, 20] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - i = i + 1 + i = 0 feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -431,18 +427,18 @@ def test_gdal_contour_fl_e(gdal_contour_path, tmp_path): ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select distinct elev from contour order by elev asc") + with ds.ExecuteSQL("select distinct elev from contour order by elev asc") as lyr: - expected_heights = [76, 81, 112, 243, 441] + expected_heights = [76, 81, 112, 243, 441] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - i = i + 1 + i = 0 feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -462,18 +458,18 @@ def test_gdal_contour_fl_ignore_off(gdal_contour_path, testdata_tif, tmp_path): ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select elev from contour order by elev asc") + with ds.ExecuteSQL("select elev from contour order by elev asc") as lyr: - expected_heights = [2, 6, 12, 16, 22] + expected_heights = [2, 6, 12, 16, 22] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - i = i + 1 + i = 0 feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -492,18 +488,18 @@ def test_gdal_contour_fl_and_i_no_dups(gdal_contour_path, testdata_tif, tmp_path ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select elev from contour order by elev asc") + with ds.ExecuteSQL("select elev from contour order by elev asc") as lyr: - expected_heights = [0, 6, 10, 16, 20] + expected_heights = [0, 6, 10, 16, 20] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - i = i + 1 + i = 0 feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -523,20 +519,20 @@ def test_gdal_contour_i_polygonize(gdal_contour_path, testdata_tif, tmp_path): ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") + with ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") as lyr: - # Raster max is 25 so the last contour is 20 (with amax of 25) - expected_heights = [0, 5, 10, 15, 20] + # Raster max is 25 so the last contour is 20 (with amax of 25) + expected_heights = [0, 5, 10, 15, 20] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - assert feat.GetField("elev2") == expected_heights[i] + 5 - i = i + 1 + i = 0 feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + assert feat.GetField("elev2") == expected_heights[i] + 5 + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -559,24 +555,24 @@ def test_gdal_contour_fl_and_i_no_dups_polygonize( ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") + with ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") as lyr: - # Raster max is 25 so the last contour is 20 (with amax of 25) - expected_heights = [0, 5, 6, 10, 15, 16, 20] + # Raster max is 25 so the last contour is 20 (with amax of 25) + expected_heights = [0, 5, 6, 10, 15, 16, 20] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - assert ( - feat.GetField("elev2") == expected_heights[i + 1] - if i < len(expected_heights) - 2 - else expected_heights[i] + 5 - ) - i = i + 1 + i = 0 feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + assert ( + feat.GetField("elev2") == expected_heights[i + 1] + if i < len(expected_heights) - 2 + else expected_heights[i] + 5 + ) + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -594,24 +590,24 @@ def test_gdal_contour_fl_e_polygonize(gdal_contour_path, tmp_path): ds = ogr.Open(contour_shp) - lyr = ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") + with ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") as lyr: - # Raster min is 75, max is 460 - expected_heights = [75, 76, 81, 112, 243, 441] + # Raster min is 75, max is 460 + expected_heights = [75, 76, 81, 112, 243, 441] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) - i = 0 - feat = lyr.GetNextFeature() - while feat is not None: - assert feat.GetField("elev") == expected_heights[i] - assert ( - feat.GetField("elev2") == expected_heights[i + 1] - if i < len(expected_heights) - 2 - else 460 - ) - i = i + 1 + i = 0 feat = lyr.GetNextFeature() + while feat is not None: + assert feat.GetField("elev") == expected_heights[i] + assert ( + feat.GetField("elev2") == expected_heights[i + 1] + if i < len(expected_heights) - 2 + else 460 + ) + i = i + 1 + feat = lyr.GetNextFeature() ############################################################################### @@ -631,9 +627,9 @@ def test_gdal_contour_gt(gdal_contour_path, tmp_path, gt): ds = ogr.Open(out_filename) - lyr = ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") + with ds.ExecuteSQL("select elev, elev2 from contour order by elev asc") as lyr: - # Raster min is 75, max is 460 - expected_heights = [75, 76, 81, 112, 243, 441] + # Raster min is 75, max is 460 + expected_heights = [75, 76, 81, 112, 243, 441] - assert lyr.GetFeatureCount() == len(expected_heights) + assert lyr.GetFeatureCount() == len(expected_heights) diff --git a/autotest/utilities/test_gdal_translate.py b/autotest/utilities/test_gdal_translate.py index 4e21ee3883d9..6d21dfc75720 100755 --- a/autotest/utilities/test_gdal_translate.py +++ b/autotest/utilities/test_gdal_translate.py @@ -588,7 +588,7 @@ def test_gdal_translate_21(gdal_translate_path, tmp_path): ############################################################################### # Test that statistics are copied only when appropriate (#3889) -# in that case, they must *NOT* be copied +# in this case, they must *NOT* be copied @pytest.mark.require_driver("HFA") @@ -604,13 +604,11 @@ def test_gdal_translate_22(gdal_translate_path, tmp_path): md = ds.GetRasterBand(1).GetMetadata() ds = None - assert ( - "STATISTICS_MINIMUM" not in md - ), "did not expected a STATISTICS_MINIMUM value." + assert "STATISTICS_MINIMUM" not in md, "did not expect a STATISTICS_MINIMUM value." assert ( "STATISTICS_HISTOBINVALUES" not in md - ), "did not expected a STATISTICS_HISTOBINVALUES value." + ), "did not expect a STATISTICS_HISTOBINVALUES value." ############################################################################### @@ -1137,3 +1135,19 @@ def test_gdal_translate_scale_and_unscale_incompatible(gdal_translate_path, tmp_ + f" -a_scale 0.0001 -a_offset 0.1 -unscale ../gcore/data/byte.tif {tmp_vsimem}/out.tif" ) assert "-a_scale/-a_offset are not applied by -unscale" in err + + +############################################################################### +# Test that invalid values of -scale are detected + + +def test_gdal_translate_scale_invalid(gdal_translate_path, tmp_path): + + outfile = tmp_path / "out.tif" + + _, err = gdaltest.runexternal_out_and_err( + f"{gdal_translate_path} -scale 0 255 6 -badarg ../gcore/data/byte.tif {outfile}" + ) + + assert "must be numeric" in err + assert not outfile.exists() diff --git a/autotest/utilities/test_gdalwarp_lib.py b/autotest/utilities/test_gdalwarp_lib.py index 3cb97c04aee6..fbfc2391540a 100755 --- a/autotest/utilities/test_gdalwarp_lib.py +++ b/autotest/utilities/test_gdalwarp_lib.py @@ -434,6 +434,19 @@ def test_gdalwarp_lib_19(testgdalwarp_gcp_tif): ds = None +############################################################################### +# Test invalid value of -et + + +def test_gdalwarp_lib_invalid_et(testgdalwarp_gcp_tif): + + with gdaltest.enable_exceptions(): + with pytest.raises(Exception, match="Failed to parse"): + gdal.Warp( + "", "../gcore/data/byte.tif", format="MEM", errorThreshold="minimal" + ) + + ############################################################################### # Test cutline from OGR datasource. diff --git a/autotest/utilities/test_ogr2ogr.py b/autotest/utilities/test_ogr2ogr.py index fea8e074ba66..791fddcf0b00 100755 --- a/autotest/utilities/test_ogr2ogr.py +++ b/autotest/utilities/test_ogr2ogr.py @@ -290,12 +290,21 @@ def test_ogr2ogr_13(ogr2ogr_path, tmp_path): def test_ogr2ogr_14(ogr2ogr_path, tmp_path): - output_shp = str(tmp_path / "poly.shp") + output_shp = tmp_path / "poly.shp" - gdaltest.runexternal( + # invalid value + _, err = gdaltest.runexternal_out_and_err( + ogr2ogr_path + f" -segmentize small_bits {output_shp} ../ogr/data/poly.shp poly" + ) + assert "Failed to parse" in err + assert not output_shp.exists() + + _, err = gdaltest.runexternal_out_and_err( ogr2ogr_path + f" -segmentize 100 {output_shp} ../ogr/data/poly.shp poly" ) + assert not err + ds = ogr.Open(output_shp) assert ds is not None and ds.GetLayer(0).GetFeatureCount() == 10 feat = ds.GetLayer(0).GetNextFeature() @@ -700,6 +709,42 @@ def test_ogr2ogr_27(ogr2ogr_path, tmp_path): ), "unexpected extent" +############################################################################### +# Test -clipdst with clip from bounding box + + +@pytest.mark.require_geos +def test_ogr2ogr_clipdst_bbox(ogr2ogr_path, tmp_path): + + output_shp = tmp_path / "poly.shp" + + xmin = 479400 + xmax = 480300 + ymin = 4764500 + ymax = 4765100 + + _, err = gdaltest.runexternal_out_and_err( + f"{ogr2ogr_path} {output_shp} ../ogr/data/poly.shp -clipdst {xmin}x {ymin} {xmax} {ymax}" + ) + + assert "cannot load dest clip geometry" in err + assert not output_shp.exists() + + _, err = gdaltest.runexternal_out_and_err( + f"{ogr2ogr_path} {output_shp} ../ogr/data/poly.shp -clipdst {xmin} {ymin} {xmax} {ymax}" + ) + + ds = ogr.Open(output_shp) + assert ds is not None and ds.GetLayer(0).GetFeatureCount() == 7 + + assert ds.GetLayer(0).GetExtent() == ( + xmin, + xmax, + ymin, + ymax, + ), "unexpected extent" + + ############################################################################### # Test -wrapdateline on linestrings diff --git a/autotest/utilities/test_ogr2ogr_lib.py b/autotest/utilities/test_ogr2ogr_lib.py index 437502751630..2526163097a2 100755 --- a/autotest/utilities/test_ogr2ogr_lib.py +++ b/autotest/utilities/test_ogr2ogr_lib.py @@ -1633,6 +1633,13 @@ def test_ogr2ogr_lib_simplify(): f.SetGeometry(ogr.CreateGeometryFromWkt("LINESTRING(0 0, 1 0, 10 0)")) src_lyr.CreateFeature(f) + with gdaltest.enable_exceptions(), pytest.raises( + Exception, match="Failed to parse" + ): + gdal.VectorTranslate( + "", src_ds, format="Memory", simplifyTolerance="reasonable" + ) + dst_ds = gdal.VectorTranslate("", src_ds, format="Memory", simplifyTolerance=5) dst_lyr = dst_ds.GetLayer(0) @@ -2916,3 +2923,38 @@ def my_handler(errorClass, errno, msg): f = lyr.GetNextFeature() assert f.GetGeometryRef().GetGeometryType() == ogr.wkbMultiLineString assert f.GetGeometryRef().GetGeometryCount() == 2 + + +############################################################################### +# Test -explodecollections on empty geometries + + +@gdaltest.enable_exceptions() +@pytest.mark.parametrize( + "input_wkt,expected_output_wkt", + [ + ("MULTIPOINT EMPTY", "POINT EMPTY"), + ("MULTIPOINT Z EMPTY", "POINT Z EMPTY"), + ("MULTIPOINT M EMPTY", "POINT M EMPTY"), + ("MULTIPOINT ZM EMPTY", "POINT ZM EMPTY"), + ("MULTILINESTRING EMPTY", "LINESTRING EMPTY"), + ("MULTIPOLYGON EMPTY", "POLYGON EMPTY"), + ("MULTICURVE EMPTY", "COMPOUNDCURVE EMPTY"), + ("MULTISURFACE EMPTY", "CURVEPOLYGON EMPTY"), + ("GEOMETRYCOLLECTION EMPTY", "GEOMETRYCOLLECTION EMPTY"), + ], +) +def test_ogr2ogr_lib_explodecollections_empty_geoms(input_wkt, expected_output_wkt): + + with gdal.GetDriverByName("Memory").Create("", 0, 0, 0, gdal.GDT_Unknown) as src_ds: + src_lyr = src_ds.CreateLayer("test") + f = ogr.Feature(src_lyr.GetLayerDefn()) + f.SetGeometryDirectly(ogr.CreateGeometryFromWkt(input_wkt)) + src_lyr.CreateFeature(f) + + out_ds = gdal.VectorTranslate( + "", src_ds, explodeCollections=True, format="Memory" + ) + out_lyr = out_ds.GetLayer(0) + f = out_lyr.GetNextFeature() + assert f.GetGeometryRef().ExportToIsoWkt() == expected_output_wkt diff --git a/cmake/helpers/CheckDependentLibraries.cmake b/cmake/helpers/CheckDependentLibraries.cmake index 830ecef9a437..536c91cf0ae3 100644 --- a/cmake/helpers/CheckDependentLibraries.cmake +++ b/cmake/helpers/CheckDependentLibraries.cmake @@ -449,6 +449,22 @@ gdal_check_package(MONGOCXX "Enable MongoDBV3 driver" CAN_DISABLE) define_find_package2(HEIF libheif/heif.h heif PKGCONFIG_NAME libheif) gdal_check_package(HEIF "HEIF >= 1.1" CAN_DISABLE) +include(CheckCXXSourceCompiles) +check_cxx_source_compiles( + " + #include + int main() + { + struct heif_image_tiling tiling; + return 0; + } + " + LIBHEIF_SUPPORTS_TILES +) +if (LIBHEIF_SUPPORTS_TILES) + set_property(TARGET HEIF::HEIF APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS "LIBHEIF_SUPPORTS_TILES") +endif () + include(CheckDependentLibrariesAVIF) include(CheckDependentLibrariesOpenJPEG) diff --git a/doc/generate-sponsor-logos.sh b/doc/generate-sponsor-logos.sh index 5c50b73897d9..5488febe19ea 100755 --- a/doc/generate-sponsor-logos.sh +++ b/doc/generate-sponsor-logos.sh @@ -19,3 +19,4 @@ inkscape --export-png=images/sponsors/logo-koordinates.png --export-width=$BRONZ inkscape --export-png=images/sponsors/logo-frontiersi.png --export-width=$BRONZE_WIDTH images/sponsors/logo-FrontierSI.svg inkscape --export-png=images/sponsors/logo-aerometrex.png --export-width=$BRONZE_WIDTH images/sponsors/logo-aerometrex.svg inkscape --export-png=images/sponsors/logo-geoczech.png --export-width=$BRONZE_WIDTH images/sponsors/logo-geoczech.svg +inkscape --export-png=images/sponsors/logo-linz.png --export-width=$BRONZE_WIDTH images/sponsors/logo-linz.svg diff --git a/doc/images/sponsors/logo-linz.png b/doc/images/sponsors/logo-linz.png new file mode 100644 index 000000000000..a1fae96f54b1 Binary files /dev/null and b/doc/images/sponsors/logo-linz.png differ diff --git a/doc/images/sponsors/logo-linz.svg b/doc/images/sponsors/logo-linz.svg new file mode 100644 index 000000000000..587ff230db6f --- /dev/null +++ b/doc/images/sponsors/logo-linz.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/source/drivers/raster/gtiff.rst b/doc/source/drivers/raster/gtiff.rst index 0ba9c0de89bc..62119b32c9df 100644 --- a/doc/source/drivers/raster/gtiff.rst +++ b/doc/source/drivers/raster/gtiff.rst @@ -485,9 +485,10 @@ This driver supports the following creation options: * ``LERC_ZSTD`` is available when ``LERC`` and ``ZSTD`` are available. * ``JXL`` is for JPEG-XL, and is only available when using internal libtiff and building GDAL against - https://github.com/libjxl/libjxl . Supported data types are ``Byte``, ``UInt16`` and ``Float32`` only. - For GDAL < 3.6.0, JXL compression may only be used alongside ``INTERLEAVE=PIXEL`` (the default) on - datasets with 4 bands or less. + https://github.com/libjxl/libjxl . It is recommended to use JXL compression with the ``TILED=YES`` creation + option and block size of 256x256, 512x512, or 1024x1024 pixels. Supported data types are ``Byte``, + ``UInt16`` and ``Float32`` only. For GDAL < 3.6.0, JXL compression may only be used alongside + ``INTERLEAVE=PIXEL`` (the default) on datasets with 4 bands or less. * ``NONE`` is the default. diff --git a/doc/source/drivers/raster/heif.rst b/doc/source/drivers/raster/heif.rst index 803e281ac8cd..a70e9e0a83c6 100644 --- a/doc/source/drivers/raster/heif.rst +++ b/doc/source/drivers/raster/heif.rst @@ -1,7 +1,7 @@ .. _raster.heif: ================================================================================ -HEIF / HEIC -- ISO/IEC 23008-12:2017 High Efficiency Image File Format +HEIF / HEIC -- ISO/IEC 23008-12 High Efficiency Image File Format ================================================================================ .. versionadded:: 3.2 @@ -18,11 +18,14 @@ iOS 11 can generate such files. libheif 1.4 or later is needed to support images with more than 8-bits per channel. +Later versions of libheif may also support one or more of AVIF (AV1 in HEIF), JPEG, JPEG 2000 and +uncompressed images depending on compile-time options. + The driver can read EXIF metadata (exposed in the ``EXIF`` metadata domain) and XMP metadata (exposed in the ``xml:XMP`` metadata domain) The driver will expose the thumbnail as an overview (when its number of bands -matches the one of the full resolution image) +matches the number of bands in the full resolution image). If a file contains several top-level images, they will be exposed as GDAL subdatasets. @@ -41,6 +44,8 @@ Driver capabilities .. supports_virtualio:: if libheif >= 1.4 +.. supports_createcopy:: + Built hints on Windows ---------------------- diff --git a/doc/source/drivers/raster/jpegxl.rst b/doc/source/drivers/raster/jpegxl.rst index 4ed85841b58f..f1453995c310 100644 --- a/doc/source/drivers/raster/jpegxl.rst +++ b/doc/source/drivers/raster/jpegxl.rst @@ -97,6 +97,8 @@ The following creation options are available: If set to NO, or in AUTO mode if the source dataset does not use JPEG compression, the regular conversion code path is taken, resulting in a lossless or lossy copy depending on the LOSSLESS setting. + AUTO mode defaults to NO, if EFFORT, DISTANCE, ALPHA_DISTANCE or QUALITY + options are used. - .. co:: EFFORT :choices: 1-9 diff --git a/doc/source/drivers/raster/vrt.rst b/doc/source/drivers/raster/vrt.rst index f22932bf3ad2..6e3112e7cfdd 100644 --- a/doc/source/drivers/raster/vrt.rst +++ b/doc/source/drivers/raster/vrt.rst @@ -58,6 +58,8 @@ This tutorial will cover the .vrt file format (suitable for users editing .vrt files), and how .vrt files may be created and manipulated programmatically for developers. +.. _raster_vrt_creation_options: + Creation options ---------------- diff --git a/doc/source/drivers/vector/gml.rst b/doc/source/drivers/vector/gml.rst index 6b05f1213f95..8486318ac09a 100644 --- a/doc/source/drivers/vector/gml.rst +++ b/doc/source/drivers/vector/gml.rst @@ -115,6 +115,12 @@ The following configuration options are available: Equivalent of :oo:`READ_MODE`. See :ref:`gml_performance`. +- .. config:: GML_DOWNLOAD_SCHEMA + :choices: YES, NO + :since: 3.10 + + Equivalent of :oo:`DOWNLOAD_SCHEMA`. + - .. config:: GML_USE_SCHEMA_IMPORT :choices: YES, NO @@ -614,8 +620,8 @@ The following open options are supported: :choices: YES, NO :default: YES - Whether to download the - remote application schema if needed (only for WFS currently). + Whether to download the remote application schema if needed + (only if the document looks like a WFS response currently). - .. oo:: REGISTRY :choices: diff --git a/doc/source/license.rst b/doc/source/license.rst index 6fd131833a8c..70b100272cd8 100644 --- a/doc/source/license.rst +++ b/doc/source/license.rst @@ -7,8 +7,7 @@ License License -------------------------------------------------------------------------------- -In general GDAL/OGR is licensed under an MIT style license with the -following terms: +In general, the GDAL/OGR source code is licensed under the MIT license, whose terms are: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -28,6 +27,13 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -The full licensing terms are available in the `LICENSE.TXT`_ file. +Some files are licensed under BSD 2-clause, BSD 3-clause or other non-copyleft +licenses. The full licensing terms are available in the `LICENSE.TXT`_ file. + +Note however that GDAL can be built against many dependencies, each of them with +their own licensing terms (possibly LGPL, GPL or proprietary), hence the use of +a GDAL binary can be subject to less permissive licensing terms than MIT, and it +is the responsibility of users to check that they comply with the overall +licensing terms. .. _`LICENSE.TXT`: https://raw.githubusercontent.com/OSGeo/gdal/master/LICENSE.TXT diff --git a/doc/source/programs/gdal_translate.rst b/doc/source/programs/gdal_translate.rst index 1b7d9b39041c..c97a9dd71f6e 100644 --- a/doc/source/programs/gdal_translate.rst +++ b/doc/source/programs/gdal_translate.rst @@ -156,23 +156,35 @@ resampling, and rescaling pixels in the process. formats that support serializing statistics computations (GeoTIFF, VRT...) Note that the values specified after :option:`-scale` are only used to compute a scale and offset to apply to the input raster values. In particular, ``src_min`` and - ``src_max`` are not used to clip input values. + ``src_max`` are not used to clip input values unless :option:`-exponent` + is also specified. + Instead of being clipped, source values that are outside the range of ``src_min`` and ``src_max`` will be scaled to values outside the range of ``dst_min`` and ``dst_max``. + If clipping without exponential scaling is desired, + ``-exponent 1`` can be used. :option:`-scale` can be repeated several times (if specified only once, it also applies to all bands of the output dataset), so as to specify per - band parameters. It is also possible to use the "-scale_bn" syntax where bn - is a band number (e.g. "-scale_2" for the 2nd band of the output dataset) + band parameters. It is also possible to use the ``-scale_bn`` syntax where bn + is a band number (e.g. ``-scale_2`` for the 2nd band of the output dataset) to specify the parameters of one or several specific bands. .. option:: -exponent - To apply non-linear scaling with a power function. exp_val is the exponent + Apply non-linear scaling with a power function. ``exp_val`` is the exponent of the power function (must be positive). This option must be used with the - -scale option. If specified only once, -exponent applies to all bands of + :option:`-scale` option. If specified only once, :option:`-exponent` applies + to all bands of the output image. It can be repeated several times so as to specify per - band parameters. It is also possible to use the "-exponent_bn" syntax where - bn is a band number (e.g. "-exponent_2" for the 2nd band of the output + band parameters. It is also possible to use the ``-exponent_bn`` syntax where + bn is a band number (e.g. ``-exponent_2`` for the 2nd band of the output dataset) to specify the parameters of one or several specific bands. + The scaled value ``Dst`` is calculated from the source value ``Src`` with the following + formula: + + .. math:: + {Dst} = \left( {Dst}_{max} - {Dst}_{min} \right) \times \operatorname{max} \left( 0, \operatorname{min} \left( 1, \left( \frac{{Src} - {Src}_{min}}{{Src}_{max}-{Src}_{min}} \right)^{exp\_val} \right) \right) + {Dst}_{min} + + .. option:: -unscale Apply the scale/offset metadata for the bands to convert scaled values to diff --git a/doc/source/programs/gdalbuildvrt.rst b/doc/source/programs/gdalbuildvrt.rst index efeba4c381c8..2a41e2899257 100644 --- a/doc/source/programs/gdalbuildvrt.rst +++ b/doc/source/programs/gdalbuildvrt.rst @@ -28,6 +28,7 @@ Synopsis [-a_srs ] [-r {nearest|bilinear|cubic|cubicspline|lanczos|average|mode}] [-oo =]... + [-co =]... [-input_file_list ] [-overwrite] [-strict | -non_strict] []... @@ -208,6 +209,12 @@ changed in later versions. .. versionadded:: 2.2 +.. option:: -co = + + Specify a :ref:`VRT driver creation option `. + + .. versionadded:: 3.10 + .. option:: -input_file_list To specify a text file with an input filename on each line diff --git a/doc/source/sponsors/index.rst b/doc/source/sponsors/index.rst index cc80b85851fb..8be481f461a8 100644 --- a/doc/source/sponsors/index.rst +++ b/doc/source/sponsors/index.rst @@ -88,6 +88,13 @@ the health of the project: :width: 150 px :target: https://www.koordinates.com + .. container:: horizontal-logo + + .. image:: ../../images/sponsors/logo-linz.png + :class: img-logos + :width: 150 px + :target: https://www.linz.govt.nz + .. container:: horizontal-logo .. image:: ../../images/sponsors/logo-mapgears.png diff --git a/frmts/dimap/dimapdataset.cpp b/frmts/dimap/dimapdataset.cpp index 3f115d909446..007b9a2024b9 100644 --- a/frmts/dimap/dimapdataset.cpp +++ b/frmts/dimap/dimapdataset.cpp @@ -1400,16 +1400,19 @@ int DIMAPDataset::ReadImageInformation2() } case 4: { + poBand->SetColorInterpretation(GCI_NIRBand); poBand->SetDescription("NIR"); break; } case 5: { + poBand->SetColorInterpretation(GCI_RedEdgeBand); poBand->SetDescription("Red Edge"); break; } case 6: { + poBand->SetColorInterpretation(GCI_CoastalBand); poBand->SetDescription("Deep Blue"); break; } @@ -1417,6 +1420,11 @@ int DIMAPDataset::ReadImageInformation2() break; } } + else if (l_nBands == 1 && osSpectralProcessing == "PAN") + { + poBand->SetColorInterpretation(GCI_PanBand); + poBand->SetDescription("Panchromatic"); + } SetBand(iBand, poBand); } diff --git a/frmts/esric/esric_dataset.cpp b/frmts/esric/esric_dataset.cpp index fe76926b5754..b3b162577c61 100644 --- a/frmts/esric/esric_dataset.cpp +++ b/frmts/esric/esric_dataset.cpp @@ -574,6 +574,9 @@ class ESRICProxyRasterBand final : public GDALProxyRasterBand class ESRICProxyDataset final : public GDALProxyDataset { private: + // m_poSrcDS must be placed before m_poUnderlyingDS for proper destruction + // as m_poUnderlyingDS references m_poSrcDS + std::unique_ptr m_poSrcDS{}; std::unique_ptr m_poUnderlyingDS{}; CPLStringList m_aosFileList{}; @@ -584,8 +587,9 @@ class ESRICProxyDataset final : public GDALProxyDataset } public: - ESRICProxyDataset(GDALDataset *poUnderlyingDS, const char *pszDescription) - : m_poUnderlyingDS(poUnderlyingDS) + ESRICProxyDataset(GDALDataset *poSrcDS, GDALDataset *poUnderlyingDS, + const char *pszDescription) + : m_poSrcDS(poSrcDS), m_poUnderlyingDS(poUnderlyingDS) { nRasterXSize = poUnderlyingDS->GetRasterXSize(); nRasterYSize = poUnderlyingDS->GetRasterYSize(); @@ -744,15 +748,15 @@ GDALDataset *ECDataset::Open(GDALOpenInfo *poOpenInfo, aosOptions.AddString(CPLSPrintf("BLOCKYSIZE=%d", ds->TSZ)); auto psOptions = GDALTranslateOptionsNew(aosOptions.List(), nullptr); - auto hDS = GDALTranslate("", GDALDataset::ToHandle(ds.release()), + auto hDS = GDALTranslate("", GDALDataset::ToHandle(ds.get()), psOptions, nullptr); GDALTranslateOptionsFree(psOptions); if (!hDS) { return nullptr; } - return new ESRICProxyDataset(GDALDataset::FromHandle(hDS), - pszDescription); + return new ESRICProxyDataset( + ds.release(), GDALDataset::FromHandle(hDS), pszDescription); } return ds.release(); } diff --git a/frmts/heif/CMakeLists.txt b/frmts/heif/CMakeLists.txt index 3ad5d9896423..2c09e77f4685 100644 --- a/frmts/heif/CMakeLists.txt +++ b/frmts/heif/CMakeLists.txt @@ -1,5 +1,5 @@ add_gdal_driver(TARGET gdal_HEIF - SOURCES heifdataset.cpp + SOURCES heifdataset.cpp heifdataset.h heifdatasetcreatecopy.cpp CORE_SOURCES heifdrivercore.cpp PLUGIN_CAPABLE NO_SHARED_SYMBOL_WITH_CORE) diff --git a/frmts/heif/heifdataset.cpp b/frmts/heif/heifdataset.cpp index 3155334838a8..819089adb15f 100644 --- a/frmts/heif/heifdataset.cpp +++ b/frmts/heif/heifdataset.cpp @@ -9,63 +9,10 @@ * SPDX-License-Identifier: MIT ****************************************************************************/ -#include "gdal_pam.h" -#include "ogr_spatialref.h" - -#include "include_libheif.h" - -#include "heifdrivercore.h" - -#include +#include "heifdataset.h" extern "C" void CPL_DLL GDALRegister_HEIF(); -// g++ -fPIC -std=c++11 frmts/heif/heifdataset.cpp -Iport -Igcore -Iogr -// -Iogr/ogrsf_frmts -I$HOME/heif/install-ubuntu-18.04/include -// -L$HOME/heif/install-ubuntu-18.04/lib -lheif -shared -o gdal_HEIF.so -L. -// -lgdal - -/************************************************************************/ -/* GDALHEIFDataset */ -/************************************************************************/ - -class GDALHEIFDataset final : public GDALPamDataset -{ - friend class GDALHEIFRasterBand; - - heif_context *m_hCtxt = nullptr; - heif_image_handle *m_hImageHandle = nullptr; - heif_image *m_hImage = nullptr; - bool m_bFailureDecoding = false; - std::vector> m_apoOvrDS{}; - bool m_bIsThumbnail = false; - -#ifdef HAS_CUSTOM_FILE_READER - heif_reader m_oReader{}; - VSILFILE *m_fpL = nullptr; - vsi_l_offset m_nSize = 0; - - static int64_t GetPositionCbk(void *userdata); - static int ReadCbk(void *data, size_t size, void *userdata); - static int SeekCbk(int64_t position, void *userdata); - static enum heif_reader_grow_status WaitForFileSizeCbk(int64_t target_size, - void *userdata); -#endif - - bool Init(GDALOpenInfo *poOpenInfo); - void ReadMetadata(); - void OpenThumbnails(); - - public: - GDALHEIFDataset(); - ~GDALHEIFDataset(); - - static GDALDataset *OpenHEIF(GDALOpenInfo *poOpenInfo); -#if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 12, 0) - static GDALDataset *OpenAVIF(GDALOpenInfo *poOpenInfo); -#endif -}; - /************************************************************************/ /* GDALHEIFRasterBand */ /************************************************************************/ @@ -126,8 +73,10 @@ GDALHEIFDataset::~GDALHEIFDataset() if (m_fpL) VSIFCloseL(m_fpL); #endif +#ifndef LIBHEIF_SUPPORTS_TILES if (m_hImage) heif_image_release(m_hImage); +#endif if (m_hImageHandle) heif_image_handle_release(m_hImageHandle); } @@ -288,6 +237,16 @@ bool GDALHEIFDataset::Init(GDALOpenInfo *poOpenInfo) return false; } +#ifdef LIBHEIF_SUPPORTS_TILES + err = heif_image_handle_get_image_tiling(m_hImageHandle, true, &m_tiling); + if (err.code != heif_error_Ok) + { + CPLError(CE_Failure, CPLE_AppDefined, "%s", + err.message ? err.message : "Cannot get image tiling"); + return false; + } +#endif + nRasterXSize = heif_image_handle_get_width(m_hImageHandle); nRasterYSize = heif_image_handle_get_height(m_hImageHandle); const int l_nBands = @@ -491,6 +450,17 @@ void GDALHEIFDataset::OpenThumbnails() poOvrDS->m_bIsThumbnail = true; poOvrDS->nRasterXSize = heif_image_handle_get_width(hThumbnailHandle); poOvrDS->nRasterYSize = heif_image_handle_get_height(hThumbnailHandle); +#ifdef LIBHEIF_SUPPORTS_TILES + auto err = heif_image_handle_get_image_tiling(hThumbnailHandle, true, + &poOvrDS->m_tiling); + if (err.code != heif_error_Ok) + { + CPLError(CE_Failure, CPLE_AppDefined, "%s", + err.message ? err.message : "Cannot get image tiling"); + heif_image_handle_release(hThumbnailHandle); + return; + } +#endif for (int i = 0; i < nBands; i++) { poOvrDS->SetBand(i + 1, new GDALHEIFRasterBand(poOvrDS.get(), i + 1)); @@ -561,7 +531,9 @@ static int HEIFDriverIdentify(GDALOpenInfo *poOpenInfo) GDALDataset *GDALHEIFDataset::OpenHEIF(GDALOpenInfo *poOpenInfo) { if (!HEIFDriverIdentify(poOpenInfo)) + { return nullptr; + } if (poOpenInfo->eAccess == GA_Update) { CPLError(CE_Failure, CPLE_NotSupported, @@ -637,14 +609,92 @@ GDALHEIFRasterBand::GDALHEIFRasterBand(GDALHEIFDataset *poDSIn, int nBandIn) "IMAGE_STRUCTURE"); } #endif + +#ifdef LIBHEIF_SUPPORTS_TILES + nBlockXSize = poDSIn->m_tiling.tile_width; + nBlockYSize = poDSIn->m_tiling.tile_height; +#else nBlockXSize = poDS->GetRasterXSize(); nBlockYSize = 1; +#endif } /************************************************************************/ /* IReadBlock() */ /************************************************************************/ +#ifdef LIBHEIF_SUPPORTS_TILES +CPLErr GDALHEIFRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, + void *pImage) +{ + GDALHEIFDataset *poGDS = static_cast(poDS); + if (poGDS->m_bFailureDecoding) + return CE_Failure; + const int nBands = poGDS->GetRasterCount(); + heif_image *hImage = nullptr; + struct heif_decoding_options *decode_options = + heif_decoding_options_alloc(); + + auto err = heif_image_handle_decode_image_tile( + poGDS->m_hImageHandle, &hImage, heif_colorspace_RGB, + nBands == 3 + ? (eDataType == GDT_UInt16 ? +#if CPL_IS_LSB + heif_chroma_interleaved_RRGGBB_LE +#else + heif_chroma_interleaved_RRGGBB_BE +#endif + : heif_chroma_interleaved_RGB) + : (eDataType == GDT_UInt16 ? +#if CPL_IS_LSB + heif_chroma_interleaved_RRGGBBAA_LE +#else + heif_chroma_interleaved_RRGGBBAA_BE +#endif + : heif_chroma_interleaved_RGBA), + decode_options, nBlockXOff, nBlockYOff); + if (err.code != heif_error_Ok) + { + CPLError(CE_Failure, CPLE_AppDefined, "%s", + err.message ? err.message : "Cannot decode image"); + poGDS->m_bFailureDecoding = true; + heif_decoding_options_free(decode_options); + return CE_Failure; + } + heif_decoding_options_free(decode_options); + int nStride = 0; + const uint8_t *pSrcData = heif_image_get_plane_readonly( + hImage, heif_channel_interleaved, &nStride); + if (eDataType == GDT_Byte) + { + for (int y = 0; y < nBlockYSize; y++) + { + for (int x = 0; x < nBlockXSize; x++) + { + size_t srcIndex = y * nStride + x * nBands + nBand - 1; + size_t outIndex = static_cast(y) * nBlockXSize + x; + (static_cast(pImage))[outIndex] = pSrcData[srcIndex]; + } + } + } + else + { + for (int y = 0; y < nBlockYSize; y++) + { + for (int x = 0; x < nBlockXSize; x++) + { + size_t srcIndex = static_cast(y) * (nStride / 2) + + x * nBands + nBand - 1; + size_t outIndex = static_cast(y) * nBlockXSize + x; + (static_cast(pImage))[outIndex] = + (reinterpret_cast(pSrcData))[srcIndex]; + } + } + } + heif_image_release(hImage); + return CE_None; +} +#else CPLErr GDALHEIFRasterBand::IReadBlock(int, int nBlockYOff, void *pImage) { GDALHEIFDataset *poGDS = static_cast(poDS); @@ -718,6 +768,7 @@ CPLErr GDALHEIFRasterBand::IReadBlock(int, int nBlockYOff, void *pImage) return CE_None; } +#endif /************************************************************************/ /* GDALRegister_HEIF() */ @@ -738,15 +789,94 @@ void GDALRegister_HEIF() HEIFDriverSetCommonMetadata(poDriver); #if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 12, 0) + if (heif_have_decoder_for_format(heif_compression_AVC)) + { + poDriver->SetMetadataItem("SUPPORTS_AVC", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_AVC)) + { + poDriver->SetMetadataItem("SUPPORTS_AVC_WRITE", "YES", "HEIF"); + } // If the AVIF dedicated driver is not available, register an AVIF driver, // called AVIF_HEIF, based on libheif, if it has AV1 decoding capabilities. if (heif_have_decoder_for_format(heif_compression_AV1)) { poDriver->SetMetadataItem("SUPPORTS_AVIF", "YES", "HEIF"); + poDriver->SetMetadataItem("SUPPORTS_AV1", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_AV1)) + { + poDriver->SetMetadataItem("SUPPORTS_AV1_WRITE", "YES", "HEIF"); + } + if (heif_have_decoder_for_format(heif_compression_HEVC)) + { + poDriver->SetMetadataItem("SUPPORTS_HEVC", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_HEVC)) + { + poDriver->SetMetadataItem("SUPPORTS_HEVC_WRITE", "YES", "HEIF"); + } + if (heif_have_decoder_for_format(heif_compression_JPEG)) + { + poDriver->SetMetadataItem("SUPPORTS_JPEG", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_JPEG)) + { + poDriver->SetMetadataItem("SUPPORTS_JPEG_WRITE", "YES", "HEIF"); + } +#if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 15, 0) + if (heif_have_decoder_for_format(heif_compression_JPEG2000)) + { + poDriver->SetMetadataItem("SUPPORTS_JPEG2000", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_JPEG2000)) + { + poDriver->SetMetadataItem("SUPPORTS_JPEG2000_WRITE", "YES", "HEIF"); } #endif - +#if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 18, 0) + if (heif_have_decoder_for_format(heif_compression_HTJ2K)) + { + poDriver->SetMetadataItem("SUPPORTS_HTJ2K", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_HTJ2K)) + { + poDriver->SetMetadataItem("SUPPORTS_HTJ2K_WRITE", "YES", "HEIF"); + } +#endif +#if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 16, 0) + if (heif_have_decoder_for_format(heif_compression_uncompressed)) + { + poDriver->SetMetadataItem("SUPPORTS_UNCOMPRESSED", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_uncompressed)) + { + poDriver->SetMetadataItem("SUPPORTS_UNCOMPRESSED_WRITE", "YES", + "HEIF"); + } +#endif +#if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 15, 0) + if (heif_have_decoder_for_format(heif_compression_VVC)) + { + poDriver->SetMetadataItem("SUPPORTS_VVC", "YES", "HEIF"); + } + if (heif_have_encoder_for_format(heif_compression_VVC)) + { + poDriver->SetMetadataItem("SUPPORTS_VVC_WRITE", "YES", "HEIF"); + } +#endif +#else + // Anything that old probably supports only HEVC + poDriver->SetMetadataItem("SUPPORTS_HEVC", "YES", "HEIF"); +#endif +#ifdef LIBHEIF_SUPPORTS_TILES + poDriver->SetMetadataItem("SUPPORTS_TILES", "YES", "HEIF"); +#endif poDriver->pfnOpen = GDALHEIFDataset::OpenHEIF; + +#ifdef HAS_CUSTOM_FILE_WRITER + poDriver->pfnCreateCopy = GDALHEIFDataset::CreateCopy; +#endif poDM->RegisterDriver(poDriver); } diff --git a/frmts/heif/heifdataset.h b/frmts/heif/heifdataset.h new file mode 100644 index 000000000000..5f8816991b62 --- /dev/null +++ b/frmts/heif/heifdataset.h @@ -0,0 +1,84 @@ +/****************************************************************************** + * + * Project: HEIF read-only Driver + * Author: Even Rouault + * + ****************************************************************************** + * Copyright (c) 2020, Even Rouault + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#ifndef HEIFDATASET_H_INCLUDED_ +#define HEIFDATASET_H_INCLUDED_ + +#include "gdal_pam.h" +#include "ogr_spatialref.h" + +#include "include_libheif.h" + +#include "heifdrivercore.h" + +#include + +/************************************************************************/ +/* GDALHEIFDataset */ +/************************************************************************/ + +class GDALHEIFDataset final : public GDALPamDataset +{ + friend class GDALHEIFRasterBand; + + heif_context *m_hCtxt = nullptr; + heif_image_handle *m_hImageHandle = nullptr; +#ifndef LIBHEIF_SUPPORTS_TILES + heif_image *m_hImage = nullptr; +#endif + bool m_bFailureDecoding = false; + std::vector> m_apoOvrDS{}; + bool m_bIsThumbnail = false; + +#ifdef LIBHEIF_SUPPORTS_TILES + heif_image_tiling m_tiling; +#endif + +#ifdef HAS_CUSTOM_FILE_READER + heif_reader m_oReader{}; + VSILFILE *m_fpL = nullptr; + vsi_l_offset m_nSize = 0; + + static int64_t GetPositionCbk(void *userdata); + static int ReadCbk(void *data, size_t size, void *userdata); + static int SeekCbk(int64_t position, void *userdata); + static enum heif_reader_grow_status WaitForFileSizeCbk(int64_t target_size, + void *userdata); +#endif + + bool Init(GDALOpenInfo *poOpenInfo); + void ReadMetadata(); + void OpenThumbnails(); + +#ifdef HAS_CUSTOM_FILE_WRITER + static heif_error VFS_WriterCallback(struct heif_context *ctx, + const void *data, size_t size, + void *userdata); +#endif + + public: + GDALHEIFDataset(); + ~GDALHEIFDataset(); + + static GDALDataset *OpenHEIF(GDALOpenInfo *poOpenInfo); +#if LIBHEIF_NUMERIC_VERSION >= BUILD_LIBHEIF_VERSION(1, 12, 0) + static GDALDataset *OpenAVIF(GDALOpenInfo *poOpenInfo); +#endif + +#ifdef HAS_CUSTOM_FILE_WRITER + static GDALDataset *CreateCopy(const char *pszFilename, + GDALDataset *poSrcDS, int bStrict, + char **papszOptions, + GDALProgressFunc pfnProgress, + void *pProgressData); +#endif +}; +#endif /* HEIFDATASET_H_INCLUDED_ */ diff --git a/frmts/heif/heifdatasetcreatecopy.cpp b/frmts/heif/heifdatasetcreatecopy.cpp new file mode 100644 index 000000000000..994504181614 --- /dev/null +++ b/frmts/heif/heifdatasetcreatecopy.cpp @@ -0,0 +1,271 @@ +/****************************************************************************** + * + * Project: HEIF Driver + * Author: Brad Hards + * + ****************************************************************************** + * Copyright (c) 2024, Brad Hards + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#include "heifdataset.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "cpl_error.h" + +#ifdef HAS_CUSTOM_FILE_WRITER + +// Same default as libheif encoder example +constexpr int DEFAULT_QUALITY = 50; + +static CPLErr mapColourInterpretation(GDALColorInterp colourInterpretation, + heif_channel *channel) +{ + switch (colourInterpretation) + { + case GCI_RedBand: + *channel = heif_channel_R; + return CE_None; + case GCI_GreenBand: + *channel = heif_channel_G; + return CE_None; + case GCI_BlueBand: + *channel = heif_channel_B; + return CE_None; + case GCI_AlphaBand: + *channel = heif_channel_Alpha; + return CE_None; + default: + return CE_Failure; + } +} + +static heif_compression_format getCompressionType(CSLConstList papszOptions) +{ + const char *pszValue = CSLFetchNameValue(papszOptions, "CODEC"); + if (pszValue == nullptr) + { + return heif_compression_HEVC; + } + if (strcmp(pszValue, "HEVC") == 0) + { + return heif_compression_HEVC; + } +#if LIBHEIF_HAVE_VERSION(1, 7, 0) + if (strcmp(pszValue, "AV1") == 0) + { + return heif_compression_AV1; + } +#endif +#if LIBHEIF_HAVE_VERSION(1, 17, 0) + if (strcmp(pszValue, "JPEG") == 0) + { + return heif_compression_JPEG; + } +#endif +#if LIBHEIF_HAVE_VERSION(1, 17, 0) + if (strcmp(pszValue, "JPEG2000") == 0) + { + return heif_compression_JPEG2000; + } +#endif +#if LIBHEIF_HAVE_VERSION(1, 16, 0) + if (strcmp(pszValue, "UNCOMPRESSED") == 0) + { + return heif_compression_uncompressed; + } +#endif +#if LIBHEIF_HAVE_VERSION(1, 18, 0) + if (strcmp(pszValue, "VVC") == 0) + { + return heif_compression_VVC; + } +#endif + CPLError(CE_Warning, CPLE_IllegalArg, + "CODEC=%s value not recognised, ignoring.", pszValue); + return heif_compression_HEVC; +} + +static void setEncoderParameters(heif_encoder *encoder, + CSLConstList papszOptions) +{ + const char *pszValue = CSLFetchNameValue(papszOptions, "QUALITY"); + int nQuality = DEFAULT_QUALITY; + if (pszValue != nullptr) + { + nQuality = atoi(pszValue); + if ((nQuality < 0) || (nQuality > 100)) + { + CPLError(CE_Warning, CPLE_IllegalArg, + "QUALITY=%s value not recognised, ignoring.", pszValue); + nQuality = DEFAULT_QUALITY; + } + } + heif_encoder_set_lossy_quality(encoder, nQuality); +} + +heif_error GDALHEIFDataset::VFS_WriterCallback(struct heif_context *, + const void *data, size_t size, + void *userdata) +{ + VSILFILE *fp = static_cast(userdata); + size_t bytesWritten = VSIFWriteL(data, 1, size, fp); + heif_error result; + if (bytesWritten == size) + { + result.code = heif_error_Ok; + result.subcode = heif_suberror_Unspecified; + result.message = "Success"; + } + else + { + result.code = heif_error_Encoding_error; + result.subcode = heif_suberror_Cannot_write_output_data; + result.message = "Not all data written"; + } + return result; +} + +/************************************************************************/ +/* CreateCopy() */ +/************************************************************************/ +GDALDataset * +GDALHEIFDataset::CreateCopy(const char *pszFilename, GDALDataset *poSrcDS, int, + CPL_UNUSED char **papszOptions, + CPL_UNUSED GDALProgressFunc pfnProgress, + CPL_UNUSED void *pProgressData) +{ + if (pfnProgress == nullptr) + pfnProgress = GDALDummyProgress; + + int nBands = poSrcDS->GetRasterCount(); + if ((nBands != 3) && (nBands != 4)) + { + CPLError(CE_Failure, CPLE_NotSupported, + "Driver only supports source dataset with 3 or 4 bands."); + return nullptr; + } + // TODO: more sanity checks + + heif_context *ctx = heif_context_alloc(); + heif_encoder *encoder; + heif_compression_format codec = getCompressionType(papszOptions); + struct heif_error err; + err = heif_context_get_encoder_for_format(ctx, codec, &encoder); + if (err.code) + { + heif_context_free(ctx); + // TODO: get the error message and printf + CPLError(CE_Failure, CPLE_AppDefined, + "Failed to create libheif encoder."); + return nullptr; + } + + setEncoderParameters(encoder, papszOptions); + + heif_image *image; + + err = + heif_image_create(poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), + heif_colorspace_RGB, heif_chroma_444, &image); + if (err.code) + { + heif_encoder_release(encoder); + heif_context_free(ctx); + CPLError(CE_Failure, CPLE_AppDefined, + "Failed to create libheif input image.\n"); + return nullptr; + } + + for (auto &&poBand : poSrcDS->GetBands()) + { + if (poBand->GetRasterDataType() != GDT_Byte) + { + heif_image_release(image); + heif_encoder_release(encoder); + heif_context_free(ctx); + CPLError(CE_Failure, CPLE_AppDefined, "Unsupported data type."); + return nullptr; + } + heif_channel channel; + auto mapError = + mapColourInterpretation(poBand->GetColorInterpretation(), &channel); + if (mapError != CE_None) + { + heif_image_release(image); + heif_encoder_release(encoder); + heif_context_free(ctx); + CPLError(CE_Failure, CPLE_NotSupported, + "Driver does not support bands other than RGBA yet."); + return nullptr; + } + err = heif_image_add_plane(image, channel, poSrcDS->GetRasterXSize(), + poSrcDS->GetRasterYSize(), 8); + if (err.code) + { + heif_image_release(image); + heif_encoder_release(encoder); + heif_context_free(ctx); + CPLError(CE_Failure, CPLE_AppDefined, + "Failed to add image plane to libheif input image."); + return nullptr; + } + int stride; + uint8_t *p = heif_image_get_plane(image, channel, &stride); + auto eErr = poBand->RasterIO( + GF_Read, 0, 0, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), + p, poSrcDS->GetRasterXSize(), poSrcDS->GetRasterYSize(), GDT_Byte, + 0, stride, nullptr); + + if (eErr != CE_None) + { + heif_image_release(image); + heif_encoder_release(encoder); + heif_context_free(ctx); + return nullptr; + } + } + + // TODO: set options based on creation options + heif_encoding_options *encoding_options = nullptr; + + heif_image_handle *out_image_handle; + + heif_context_encode_image(ctx, image, encoder, encoding_options, + &out_image_handle); + + heif_image_release(image); + + // TODO: set properties on output image + heif_image_handle_release(out_image_handle); + heif_encoding_options_free(encoding_options); + heif_encoder_release(encoder); + + VSILFILE *fp = VSIFOpenL(pszFilename, "wb"); + if (fp == nullptr) + { + ReportError(pszFilename, CE_Failure, CPLE_OpenFailed, + "Unable to create file."); + heif_context_free(ctx); + return nullptr; + } + heif_writer writer; + writer.writer_api_version = 1; + writer.write = VFS_WriterCallback; + heif_context_write(ctx, &writer, fp); + VSIFCloseL(fp); + + heif_context_free(ctx); + + return GDALDataset::Open(pszFilename); +} +#endif diff --git a/frmts/heif/heifdrivercore.cpp b/frmts/heif/heifdrivercore.cpp index 012646d0860c..fa27d8336cec 100644 --- a/frmts/heif/heifdrivercore.cpp +++ b/frmts/heif/heifdrivercore.cpp @@ -21,7 +21,7 @@ static const GByte FTYP_4CC[] = {'f', 't', 'y', 'p'}; static const GByte supportedBrands[][4]{ {'a', 'v', 'i', 'f'}, {'h', 'e', 'i', 'c'}, {'h', 'e', 'i', 'x'}, {'j', '2', 'k', 'i'}, {'j', 'p', 'e', 'g'}, {'m', 'i', 'a', 'f'}, - {'m', 'i', 'f', '1'}, {'m', 'i', 'f', '2'}}; + {'m', 'i', 'f', '1'}, {'m', 'i', 'f', '2'}, {'v', 'v', 'i', 'c'}}; int HEIFDriverIdentifySimplified(GDALOpenInfo *poOpenInfo) { @@ -86,6 +86,9 @@ void HEIFDriverSetCommonMetadata(GDALDriver *poDriver) poDriver->pfnIdentify = HEIFDriverIdentifySimplified; poDriver->SetMetadataItem(GDAL_DCAP_OPEN, "YES"); +#ifdef HAS_CUSTOM_FILE_WRITER + poDriver->SetMetadataItem(GDAL_DCAP_CREATECOPY, "YES"); +#endif } /************************************************************************/ diff --git a/frmts/heif/include_libheif.h b/frmts/heif/include_libheif.h index ed7626808744..07c1d45711fe 100644 --- a/frmts/heif/include_libheif.h +++ b/frmts/heif/include_libheif.h @@ -21,4 +21,10 @@ #define HAS_CUSTOM_FILE_READER #endif +#if LIBHEIF_HAVE_VERSION(1, 1, 0) +#define HAS_CUSTOM_FILE_WRITER +#endif + +#include + #endif diff --git a/frmts/jp2lura/jp2luradrivercore.cpp b/frmts/jp2lura/jp2luradrivercore.cpp index 0ceba183deee..cbd5d721f8af 100644 --- a/frmts/jp2lura/jp2luradrivercore.cpp +++ b/frmts/jp2lura/jp2luradrivercore.cpp @@ -20,15 +20,11 @@ int JP2LuraDriverIdentify(GDALOpenInfo *poOpenInfo) { - if (STARTS_WITH_CI(poOpenInfo->pszFilename, "JP2Lura:")) - return true; - - // Check magic number - return poOpenInfo->fpL != nullptr && poOpenInfo->nHeaderBytes >= 4 && - poOpenInfo->pabyHeader[0] == 0x76 && - poOpenInfo->pabyHeader[1] == 0x2f && - poOpenInfo->pabyHeader[2] == 0x31 && - poOpenInfo->pabyHeader[3] == 0x01; + return poOpenInfo->nHeaderBytes >= 16 && + (memcmp(poOpenInfo->pabyHeader, jpc_header, sizeof(jpc_header)) == + 0 || + memcmp(poOpenInfo->pabyHeader + 4, jp2_box_jp, + sizeof(jp2_box_jp)) == 0); } /************************************************************************/ diff --git a/frmts/jpegxl/jpegxl.cpp b/frmts/jpegxl/jpegxl.cpp index 0ac0f3e1925d..46befd797728 100644 --- a/frmts/jpegxl/jpegxl.cpp +++ b/frmts/jpegxl/jpegxl.cpp @@ -1798,16 +1798,35 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, } } + constexpr int DEFAULT_EFFORT = 5; + const int nEffort = atoi(CSLFetchNameValueDef( + papszOptions, "EFFORT", CPLSPrintf("%d", DEFAULT_EFFORT))); + const char *pszDistance = CSLFetchNameValue(papszOptions, "DISTANCE"); + const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY"); + const char *pszAlphaDistance = + CSLFetchNameValue(papszOptions, "ALPHA_DISTANCE"); const char *pszLossLessCopy = CSLFetchNameValueDef(papszOptions, "LOSSLESS_COPY", "AUTO"); - if (EQUAL(pszLossLessCopy, "AUTO") || CPLTestBool(pszLossLessCopy)) + if ((EQUAL(pszLossLessCopy, "AUTO") && !pszDistance && !pszQuality && + !pszAlphaDistance && nEffort == DEFAULT_EFFORT) || + (!EQUAL(pszLossLessCopy, "AUTO") && CPLTestBool(pszLossLessCopy))) { void *pJPEGXLContent = nullptr; size_t nJPEGXLContent = 0; - if (poSrcDS->ReadCompressedData( - "JXL", 0, 0, poSrcDS->GetRasterXSize(), - poSrcDS->GetRasterYSize(), poSrcDS->GetRasterCount(), nullptr, - &pJPEGXLContent, &nJPEGXLContent, nullptr) == CE_None) + const bool bSrcIsJXL = + (poSrcDS->ReadCompressedData( + "JXL", 0, 0, poSrcDS->GetRasterXSize(), + poSrcDS->GetRasterYSize(), poSrcDS->GetRasterCount(), nullptr, + &pJPEGXLContent, &nJPEGXLContent, nullptr) == CE_None); + if (bSrcIsJXL && (pszDistance || pszQuality || pszAlphaDistance || + nEffort != DEFAULT_EFFORT)) + { + CPLError(CE_Failure, CPLE_NotSupported, + "LOSSLESS_COPY=YES not supported when EFFORT, QUALITY, " + "DISTANCE or ALPHA_DISTANCE are specified"); + return nullptr; + } + else if (bSrcIsJXL) { CPLDebug("JPEGXL", "Lossless copy from source dataset"); GByte abySizeAndBoxName[8]; @@ -2062,10 +2081,6 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, } const char *pszLossLess = CSLFetchNameValue(papszOptions, "LOSSLESS"); - const char *pszDistance = CSLFetchNameValue(papszOptions, "DISTANCE"); - const char *pszQuality = CSLFetchNameValue(papszOptions, "QUALITY"); - const char *pszAlphaDistance = - CSLFetchNameValue(papszOptions, "ALPHA_DISTANCE"); const bool bLossless = (pszLossLess == nullptr && pszDistance == nullptr && pszQuality == nullptr) || @@ -2325,7 +2340,6 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, } } - const int nEffort = atoi(CSLFetchNameValueDef(papszOptions, "EFFORT", "5")); #ifdef HAVE_JxlEncoderFrameSettingsSetOption if (JxlEncoderFrameSettingsSetOption(opts, JXL_ENC_FRAME_SETTING_EFFORT, nEffort) != JXL_ENC_SUCCESS) @@ -2572,6 +2586,17 @@ GDALDataset *JPEGXLDataset::CreateCopy(const char *pszFilename, return nullptr; } } + else if (!bLossless) + { + // By default libjxl applies lossless encoding for extra channels + if (JXL_ENC_SUCCESS != + JxlEncoderSetExtraChannelDistance(opts, nIndex, fDistance)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "JxlEncoderSetExtraChannelDistance failed"); + return nullptr; + } + } #endif } } diff --git a/frmts/pdf/gdal_pdf.h b/frmts/pdf/gdal_pdf.h index 04f7243b13fc..800ea8d6d18e 100644 --- a/frmts/pdf/gdal_pdf.h +++ b/frmts/pdf/gdal_pdf.h @@ -372,7 +372,8 @@ class PDFDataset final : public GDALPamDataset void ExploreContentsNonStructuredInternal( GDALPDFObject *poContents, GDALPDFObject *poResources, - std::map &oMapPropertyToLayer, + const std::map &oMapPropertyToLayer, + const std::map, OGRPDFLayer *> &oMapNumGenToLayer, OGRPDFLayer *poSingleLayer); void ExploreContentsNonStructured(GDALPDFObject *poObj, GDALPDFObject *poResources); @@ -391,11 +392,13 @@ class PDFDataset final : public GDALPamDataset void ApplyMatrix(double adfCoords[2]) const; }; - OGRGeometry * - ParseContent(const char *pszContent, GDALPDFObject *poResources, - int bInitBDCStack, int bMatchQ, - std::map &oMapPropertyToLayer, - const GraphicState &graphicStateIn, OGRPDFLayer *poCurLayer); + OGRGeometry *ParseContent( + const char *pszContent, GDALPDFObject *poResources, + bool bCollectAllObjects, bool bInitBDCStack, bool bMatchQ, + const std::map &oMapPropertyToLayer, + const std::map, OGRPDFLayer *> &oMapNumGenToLayer, + const GraphicState &graphicStateIn, OGRPDFLayer *poCurLayer, + int nRecLevel); OGRGeometry *BuildGeometry(std::vector &oCoords, int bHasFoundFill, int bHasMultiPart); diff --git a/frmts/pdf/pdfdataset.cpp b/frmts/pdf/pdfdataset.cpp index 91b0317bcd2b..d481b1fdc158 100644 --- a/frmts/pdf/pdfdataset.cpp +++ b/frmts/pdf/pdfdataset.cpp @@ -7678,6 +7678,8 @@ CPLString PDFSanitizeLayerName(const char *pszName) else if (pszName[i] != '"') osName += pszName[i]; } + if (osName.empty()) + osName = "unnamed"; return osName; } diff --git a/frmts/pdf/pdfobject.cpp b/frmts/pdf/pdfobject.cpp index 8607700f86d5..9b04668946b9 100644 --- a/frmts/pdf/pdfobject.cpp +++ b/frmts/pdf/pdfobject.cpp @@ -100,9 +100,9 @@ static std::string GDALPDFGetUTF8StringFromBytes(const GByte *pabySrc, size_t nLen) { const bool bLEUnicodeMarker = - nLen > 2 && pabySrc[0] == 0xFE && pabySrc[1] == 0xFF; + nLen >= 2 && pabySrc[0] == 0xFE && pabySrc[1] == 0xFF; const bool bBEUnicodeMarker = - nLen > 2 && pabySrc[0] == 0xFF && pabySrc[1] == 0xFE; + nLen >= 2 && pabySrc[0] == 0xFF && pabySrc[1] == 0xFE; if (!bLEUnicodeMarker && !bBEUnicodeMarker) { std::string osStr; @@ -1126,10 +1126,10 @@ const std::string &GDALPDFObjectPoppler::GetString() const GooString *gooString = m_po->getString(); const std::string &osStdStr = gooString->toStr(); const bool bLEUnicodeMarker = - osStdStr.size() > 2 && static_cast(osStdStr[0]) == 0xFE && + osStdStr.size() >= 2 && static_cast(osStdStr[0]) == 0xFE && static_cast(osStdStr[1]) == 0xFF; const bool bBEUnicodeMarker = - osStdStr.size() > 2 && static_cast(osStdStr[0]) == 0xFF && + osStdStr.size() >= 2 && static_cast(osStdStr[0]) == 0xFF && static_cast(osStdStr[1]) == 0xFE; if (!bLEUnicodeMarker && !bBEUnicodeMarker) { diff --git a/frmts/pdf/pdfreadvectors.cpp b/frmts/pdf/pdfreadvectors.cpp index 4613d374f486..d59fbe0ac860 100644 --- a/frmts/pdf/pdfreadvectors.cpp +++ b/frmts/pdf/pdfreadvectors.cpp @@ -647,10 +647,18 @@ static void AddBezierCurve(std::vector &oCoords, const double *x0_y0, #define FILL_SUBPATH -97 OGRGeometry *PDFDataset::ParseContent( - const char *pszContent, GDALPDFObject *poResources, int bInitBDCStack, - int bMatchQ, std::map &oMapPropertyToLayer, - const GraphicState &graphicStateIn, OGRPDFLayer *poCurLayer) + const char *pszContent, GDALPDFObject *poResources, bool bCollectAllObjects, + bool bInitBDCStack, bool bMatchQ, + const std::map &oMapPropertyToLayer, + const std::map, OGRPDFLayer *> &oMapNumGenToLayer, + const GraphicState &graphicStateIn, OGRPDFLayer *poCurLayer, int nRecLevel) { + if (nRecLevel == 32) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Too many recursion levels in ParseContent()"); + return nullptr; + } if (CPLTestBool(CPLGetConfigOption("PDF_DUMP_CONTENT", "NO"))) { static int counter = 1; @@ -704,9 +712,6 @@ OGRGeometry *PDFDataset::ParseContent( int nArrayLevel = 0; int nBTLevel = 0; - int bCollectAllObjects = - poResources != nullptr && !bInitBDCStack && !bMatchQ; - GraphicState oGS(graphicStateIn); std::stack oGSStack; std::stack oLayerStack; @@ -1304,31 +1309,27 @@ OGRGeometry *PDFDataset::ParseContent( return nullptr; } - if (poResources == nullptr) + if (osObjectName.find("/SymImage") == 0) { - if (osObjectName.find("/SymImage") == 0) - { - oCoords.push_back(oGS.adfCM[4] + oGS.adfCM[0] / 2); - oCoords.push_back(oGS.adfCM[5] + oGS.adfCM[3] / 2); + oCoords.push_back(oGS.adfCM[4] + oGS.adfCM[0] / 2); + oCoords.push_back(oGS.adfCM[5] + oGS.adfCM[3] / 2); - szToken[0] = '\0'; - nTokenSize = 0; + szToken[0] = '\0'; + nTokenSize = 0; - if (poCurLayer != nullptr) - bEmitFeature = TRUE; - else - continue; - } + if (poCurLayer != nullptr) + bEmitFeature = TRUE; else - { - szToken[0] = '\0'; - nTokenSize = 0; - - CPLDebug("PDF", - "Skipping unknown object %s at line %d", - osObjectName.c_str(), nLineNumber); continue; - } + } + else if (poResources == nullptr) + { + szToken[0] = '\0'; + nTokenSize = 0; + + CPLDebug("PDF", "Skipping unknown object %s at line %d", + osObjectName.c_str(), nLineNumber); + continue; } if (!bEmitFeature) @@ -1366,6 +1367,7 @@ OGRGeometry *PDFDataset::ParseContent( } int bParseStream = TRUE; + GDALPDFObject *poSubResources = nullptr; /* Check if the object is an image. If so, no need to * try to parse */ /* it. */ @@ -1379,6 +1381,14 @@ OGRGeometry *PDFDataset::ParseContent( { bParseStream = FALSE; } + + poSubResources = + poObject->GetDictionary()->Get("Resources"); + if (poSubResources && poSubResources->GetType() != + PDFObjectType_Dictionary) + { + poSubResources = nullptr; + } } if (bParseStream) @@ -1398,12 +1408,42 @@ OGRGeometry *PDFDataset::ParseContent( return nullptr; } + OGRPDFLayer *poCurLayerRec = poCurLayer; + + if (poObject->GetType() == PDFObjectType_Dictionary) + { + auto poOC = + poObject->GetDictionary()->Get("OC"); + if (poOC && + poOC->GetType() == + PDFObjectType_Dictionary && + poOC->GetRefNum().toBool()) + { + const auto oIterNumGenToLayer = + oMapNumGenToLayer.find( + std::pair(poOC->GetRefNum().toInt(), + poOC->GetRefGen())); + if (oIterNumGenToLayer != + oMapNumGenToLayer.end()) + { + poCurLayerRec = + oIterNumGenToLayer->second; + } + } + } + char *pszStr = poStream->GetBytes(); if (pszStr) { + CPLDebug("PDF", "Starting parsing %s", + osObjectName.c_str()); OGRGeometry *poGeom = ParseContent( - pszStr, nullptr, FALSE, FALSE, - oMapPropertyToLayer, oGS, poCurLayer); + pszStr, poSubResources, bCollectAllObjects, + false, false, oMapPropertyToLayer, + oMapNumGenToLayer, oGS, poCurLayerRec, + nRecLevel + 1); + CPLDebug("PDF", "End of parsing of %s", + osObjectName.c_str()); CPLFree(pszStr); if (poGeom && !bCollectAllObjects) return poGeom; @@ -1930,8 +1970,8 @@ void PDFDataset::ExploreContents(GDALPDFObject *poObj, if (GetGeometryFromMCID(nMCID) == nullptr) { OGRGeometry *poGeom = ParseContent( - pszStartParsing, poResources, !bMatchQ, bMatchQ, - oMapPropertyToLayer, GraphicState(), nullptr); + pszStartParsing, poResources, false, !bMatchQ, bMatchQ, + oMapPropertyToLayer, {}, GraphicState(), nullptr, 0); if (poGeom != nullptr) { /* Save geometry in map */ @@ -1950,7 +1990,8 @@ void PDFDataset::ExploreContents(GDALPDFObject *poObj, void PDFDataset::ExploreContentsNonStructuredInternal( GDALPDFObject *poContents, GDALPDFObject *poResources, - std::map &oMapPropertyToLayer, + const std::map &oMapPropertyToLayer, + const std::map, OGRPDFLayer *> &oMapNumGenToLayer, OGRPDFLayer *poSingleLayer) { if (poContents->GetType() == PDFObjectType_Array) @@ -1984,8 +2025,9 @@ void PDFDataset::ExploreContentsNonStructuredInternal( CPLFree(pszStr); } if (pszConcatStr) - ParseContent(pszConcatStr, poResources, FALSE, FALSE, - oMapPropertyToLayer, GraphicState(), poSingleLayer); + ParseContent(pszConcatStr, poResources, poResources != nullptr, + false, false, oMapPropertyToLayer, oMapNumGenToLayer, + GraphicState(), poSingleLayer, 0); CPLFree(pszConcatStr); return; } @@ -2000,8 +2042,9 @@ void PDFDataset::ExploreContentsNonStructuredInternal( char *pszStr = poStream->GetBytes(); if (!pszStr) return; - ParseContent(pszStr, poResources, FALSE, FALSE, oMapPropertyToLayer, - GraphicState(), poSingleLayer); + ParseContent(pszStr, poResources, poResources != nullptr, false, false, + oMapPropertyToLayer, oMapNumGenToLayer, GraphicState(), + poSingleLayer, 0); CPLFree(pszStr); } @@ -2339,8 +2382,9 @@ void PDFDataset::ExploreContentsNonStructured(GDALPDFObject *poContents, OGRPDFLayer *poSingleLayer = nullptr; if (m_apoLayers.empty()) { - if (CPLTestBool( - CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", "NO"))) + const char *pszReadNonStructured = + CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", nullptr); + if (pszReadNonStructured && CPLTestBool(pszReadNonStructured)) { auto poLayer = std::make_unique(this, "content", nullptr, wkbUnknown); @@ -2349,12 +2393,22 @@ void PDFDataset::ExploreContentsNonStructured(GDALPDFObject *poContents, } else { + if (!pszReadNonStructured) + { + CPLDebug( + "PDF", + "No structured content nor PDF layers detected, hence " + "vector content detection is disabled. You may force " + "vector content detection by setting the " + "OGR_PDF_READ_NON_STRUCTURED configuration option to YES"); + } return; } } ExploreContentsNonStructuredInternal(poContents, poResources, - oMapPropertyToLayer, poSingleLayer); + oMapPropertyToLayer, oMapNumGenToLayer, + poSingleLayer); /* Remove empty layers */ for (auto oIter = m_apoLayers.begin(); oIter != m_apoLayers.end(); diff --git a/frmts/snap_tiff/snaptiffdriver.cpp b/frmts/snap_tiff/snaptiffdriver.cpp index e5e491187d9a..99648dee14b9 100644 --- a/frmts/snap_tiff/snaptiffdriver.cpp +++ b/frmts/snap_tiff/snaptiffdriver.cpp @@ -716,8 +716,11 @@ void GDALRegister_SNAP_TIFF() "Sentinel Application Processing GeoTIFF"); poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/snap_tiff.html"); - poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/tiff"); - poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "tif tiff"); + // Declaring the tif extension confuses QGIS + // Cf https://github.com/qgis/QGIS/issues/59112 + // This driver is of too marginal usage to justify causing chaos downstream. + // poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/tiff"); + // poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "tif tiff"); poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES"); poDriver->pfnOpen = SNAPTIFFDataset::Open; diff --git a/frmts/zarr/zarrdriver.cpp b/frmts/zarr/zarrdriver.cpp index bca6571856f9..08c1adbc86c1 100644 --- a/frmts/zarr/zarrdriver.cpp +++ b/frmts/zarr/zarrdriver.cpp @@ -996,6 +996,10 @@ GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize, int nBandsIn, GDALDataType eType, char **papszOptions) { + // To avoid any issue with short-lived string that would be passed to us + const std::string osName = pszName; + pszName = osName.c_str(); + if (nBandsIn <= 0 || nXSize <= 0 || nYSize <= 0) { CPLError(CE_Failure, CPLE_NotSupported, @@ -1028,6 +1032,22 @@ GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize, } else { + VSIStatBufL sStat; + const bool bExists = VSIStatL(pszName, &sStat) == 0; + const bool bIsFile = bExists && !VSI_ISDIR(sStat.st_mode); + const bool bIsDirectory = + !bIsFile && ((bExists && VSI_ISDIR(sStat.st_mode)) || + !CPLStringList(VSIReadDirEx(pszName, 1)).empty()); + if (bIsFile || bIsDirectory || bExists) + { + CPLError(CE_Failure, CPLE_FileIO, "%s %s already exists.", + bIsFile ? "File" + : bIsDirectory ? "Directory" + : "Object", + pszName); + return nullptr; + } + const char *pszFormat = CSLFetchNameValueDef(papszOptions, "FORMAT", "ZARR_V2"); auto poSharedResource = @@ -1059,6 +1079,50 @@ GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize, poDS->nRasterXSize = nXSize; poDS->eAccess = GA_Update; + const auto CleanupCreatedFiles = + [bAppendSubDS, pszName, pszArrayName, &poRG, &poDS]() + { + // Make sure all objects are released so that ZarrSharedResource + // is finalized and all files are serialized. + poRG.reset(); + poDS.reset(); + + if (bAppendSubDS) + { + VSIRmdir(CPLFormFilename(pszName, pszArrayName, nullptr)); + } + else + { + // Be a bit careful before wiping too much stuff... + // At most 5 files expected for ZARR_V2: .zgroup, .zmetadata, + // one (empty) subdir, . and .. + // and for ZARR_V3: zarr.json, one (empty) subdir, . and .. + const CPLStringList aosFiles(VSIReadDirEx(pszName, 6)); + if (aosFiles.size() < 6) + { + for (const char *pszFile : aosFiles) + { + if (pszArrayName && strcmp(pszFile, pszArrayName) == 0) + { + VSIRmdir(CPLFormFilename(pszName, pszFile, nullptr)); + } + else if (!pszArrayName && + strcmp(pszFile, CPLGetBasename(pszName)) == 0) + { + VSIRmdir(CPLFormFilename(pszName, pszFile, nullptr)); + } + else if (strcmp(pszFile, ".zgroup") == 0 || + strcmp(pszFile, ".zmetadata") == 0 || + strcmp(pszFile, "zarr.json") == 0) + { + VSIUnlink(CPLFormFilename(pszName, pszFile, nullptr)); + } + } + VSIRmdir(pszName); + } + } + }; + if (bAppendSubDS) { auto aoDims = poRG->GetDimensions(); @@ -1096,7 +1160,10 @@ GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize, poRG->CreateDimension("X", std::string(), std::string(), nXSize); } if (poDS->m_poDimY == nullptr || poDS->m_poDimX == nullptr) + { + CleanupCreatedFiles(); return nullptr; + } const bool bSingleArray = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SINGLE_ARRAY", "YES")); @@ -1123,7 +1190,10 @@ GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize, pszNonNullArrayName, apoDims, GDALExtendedDataType::Create(eType), papszOptions); if (!poDS->m_poSingleArray) + { + CleanupCreatedFiles(); return nullptr; + } poDS->SetMetadataItem("INTERLEAVE", bBandInterleave ? "BAND" : "PIXEL", "IMAGE_STRUCTURE"); for (int i = 0; i < nBandsIn; i++) @@ -1145,7 +1215,10 @@ GDALDataset *ZarrDataset::Create(const char *pszName, int nXSize, int nYSize, : CPLSPrintf("Band%d", i + 1), apoDims, GDALExtendedDataType::Create(eType), papszOptions); if (poArray == nullptr) + { + CleanupCreatedFiles(); return nullptr; + } poDS->SetBand(i + 1, new ZarrRasterBand(poArray)); } } diff --git a/gcore/CMakeLists.txt b/gcore/CMakeLists.txt index a61970e9e1f5..a4699856b6fb 100644 --- a/gcore/CMakeLists.txt +++ b/gcore/CMakeLists.txt @@ -194,6 +194,7 @@ target_public_header( gdalgeorefpamdataset.h gdal_mdreader.h gdalsubdatasetinfo.h + gdal_typetraits.h ) set(GDAL_DATA_FILES diff --git a/gcore/gdal_typetraits.h b/gcore/gdal_typetraits.h new file mode 100644 index 000000000000..f7e341f49e7c --- /dev/null +++ b/gcore/gdal_typetraits.h @@ -0,0 +1,510 @@ +/****************************************************************************** + * Name: gdal_typetraits.h + * Project: GDAL Core + * Purpose: Type traits for mapping C++ types to and from GDAL/OGR types. + * Author: Robin Princeley, + * + ****************************************************************************** + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + ****************************************************************************/ +#if !defined(GDAL_TYPETRAITS_H_INCLUDED) +#define GDAL_TYPETRAITS_H_INCLUDED + +#include "gdal_priv.h" + +// NOTE: below GDAL_ENABLE_FLOAT16 is not guaranteed to be stable and is +// mostly for Esri internal needs for now. Might be revisited if/once RFC 100 +// (https://github.com/OSGeo/gdal/pull/10146) is adopted. +#ifdef GDAL_ENABLE_FLOAT16 +#if defined(__GNUC__) || defined(__clang__) +#define __STDC_WANT_IEC_60559_TYPES_EXT__ +#include // Also brings in _Float16 +#endif +#endif + +#include + +namespace gdal +{ + +/** Trait accepting a C++ type ([u]int[8/16/32/64], float, double, + * std::complex, std::complex or std::string) + * and mapping it to GDALDataType / OGRFieldType. + * + * Each specialization has the following members: + * static constexpr GDALDataType gdal_type; + * static constexpr size_t size; + * static constexpr OGRFieldType ogr_type; + * static constexpr OGRFieldSubType ogr_subtype; + * + * @since 3.11 + */ +template struct CXXTypeTraits +{ +}; + +//! @cond Doxygen_Suppress +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Int8; + static constexpr size_t size = sizeof(int8_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int8); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Byte; + static constexpr size_t size = sizeof(uint8_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Byte); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Int16; + static constexpr size_t size = sizeof(int16_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTInt16; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int16); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_UInt16; + static constexpr size_t size = sizeof(uint16_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_UInt16); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Int32; + static constexpr size_t size = sizeof(int32_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int32); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_UInt32; + static constexpr size_t size = sizeof(uint32_t); + static constexpr OGRFieldType ogr_type = OFTInteger64; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_UInt32); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Int64; + static constexpr size_t size = sizeof(int64_t); + static constexpr OGRFieldType ogr_type = OFTInteger64; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int64); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_UInt64; + static constexpr size_t size = sizeof(uint64_t); + // Mapping to Real is questionable... + static constexpr OGRFieldType ogr_type = OFTReal; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_UInt64); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Float32; + static constexpr size_t size = sizeof(float); + static constexpr OGRFieldType ogr_type = OFTReal; + static constexpr OGRFieldSubType ogr_subtype = OFSTFloat32; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Float32); + } +}; + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Float64; + static constexpr size_t size = sizeof(double); + static constexpr OGRFieldType ogr_type = OFTReal; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Float64); + } +}; + +template <> struct CXXTypeTraits> +{ + static constexpr GDALDataType gdal_type = GDT_CFloat32; + static constexpr size_t size = sizeof(float) * 2; + static constexpr OGRFieldType ogr_type = OFTMaxType; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_CFloat32); + } +}; + +template <> struct CXXTypeTraits> +{ + static constexpr GDALDataType gdal_type = GDT_CFloat64; + static constexpr size_t size = sizeof(double) * 2; + static constexpr OGRFieldType ogr_type = OFTMaxType; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_CFloat64); + } +}; + +#if defined(GDAL_ENABLE_FLOAT16) && defined(FLT16_MAX) && defined(FLT16_MIN) +template <> struct CXXTypeTraits<_Float16> +{ + static constexpr GDALDataType gdal_type = GDT_Float16; + static constexpr size_t size = sizeof(_Float16); + static constexpr OGRFieldType ogr_type = OFTReal; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Float16); + } +}; +#endif + +template <> struct CXXTypeTraits +{ + static constexpr GDALDataType gdal_type = GDT_Unknown; + static constexpr size_t size = 0; + static constexpr OGRFieldType ogr_type = OFTString; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::CreateString(); + } +}; + +//! @endcond + +/** Trait accepting a GDALDataType and mapping it to corresponding C++ type and + * OGRFieldType + * + * Each specialization has the following members: + * typedef T type; (except for GDT_CInt16 and GDT_CInt32) + * static constexpr size_t size; + * static constexpr OGRFieldType ogr_type; + * static constexpr OGRFieldSubType ogr_subtype; + * static inline GDALExtendedDataType GetExtendedDataType(); + * + * @since 3.11 + */ +template struct GDALDataTypeTraits +{ +}; + +//! @cond Doxygen_Suppress +template <> struct GDALDataTypeTraits +{ + typedef int8_t type; + static constexpr size_t size = sizeof(int8_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int8); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef uint8_t type; + static constexpr size_t size = sizeof(uint8_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Byte); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef int16_t type; + static constexpr size_t size = sizeof(int16_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTInt16; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int16); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef uint16_t type; + static constexpr size_t size = sizeof(uint16_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_UInt16); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef int32_t type; + static constexpr size_t size = sizeof(int32_t); + static constexpr OGRFieldType ogr_type = OFTInteger; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int32); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef uint32_t type; + static constexpr size_t size = sizeof(uint32_t); + static constexpr OGRFieldType ogr_type = OFTInteger64; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_UInt32); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef int64_t type; + static constexpr size_t size = sizeof(int64_t); + static constexpr OGRFieldType ogr_type = OFTInteger64; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Int64); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef uint64_t type; + static constexpr size_t size = sizeof(uint64_t); + // Mapping to Real is questionable... + static constexpr OGRFieldType ogr_type = OFTReal; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_UInt64); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef float type; + static constexpr size_t size = sizeof(float); + static constexpr OGRFieldType ogr_type = OFTReal; + static constexpr OGRFieldSubType ogr_subtype = OFSTFloat32; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Float32); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef double type; + static constexpr size_t size = sizeof(double); + static constexpr OGRFieldType ogr_type = OFTReal; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_Float64); + } +}; + +template <> struct GDALDataTypeTraits +{ + // typedef type not available ! + static constexpr size_t size = sizeof(int16_t) * 2; + static constexpr OGRFieldType ogr_type = OFTMaxType; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_CInt16); + } +}; + +template <> struct GDALDataTypeTraits +{ + // typedef type not available ! + static constexpr size_t size = sizeof(int32_t) * 2; + static constexpr OGRFieldType ogr_type = OFTMaxType; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_CInt32); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef std::complex type; + static constexpr size_t size = sizeof(float) * 2; + static constexpr OGRFieldType ogr_type = OFTMaxType; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_CFloat32); + } +}; + +template <> struct GDALDataTypeTraits +{ + typedef std::complex type; + static constexpr size_t size = sizeof(double) * 2; + static constexpr OGRFieldType ogr_type = OFTMaxType; + static constexpr OGRFieldSubType ogr_subtype = OFSTNone; + + static inline GDALExtendedDataType GetExtendedDataType() + { + return GDALExtendedDataType::Create(GDT_CFloat64); + } +}; + +//! @endcond + +/** Map a GDALDataType to the most suitable OGRFieldType. + * + * Note that GDT_UInt32 is mapped to OFTInteger64 to avoid data losses. + * GDT_UInt64 is mapped to OFTReal, which can be lossy. If values are + * guaranteed to be in [0, INT64_MAX] range, callers might want to use + * OFTInteger64 instead. + * There is no mapping for complex data types. + * + * @since 3.11 + */ +inline OGRFieldType GetOGRFieldType(const GDALDataType gdal_type) +{ + switch (gdal_type) + { + case GDT_Byte: + case GDT_Int8: + case GDT_Int16: + case GDT_Int32: + case GDT_UInt16: + return OFTInteger; + case GDT_UInt32: + case GDT_Int64: + return OFTInteger64; + case GDT_UInt64: // Questionable + case GDT_Float32: + case GDT_Float64: + return OFTReal; + case GDT_CInt16: + case GDT_CInt32: + case GDT_CFloat32: + case GDT_CFloat64: + case GDT_Unknown: + case GDT_TypeCount: + break; + } + return OFTMaxType; +} + +/** Map a GDALExtendedDataType to the most suitable OGRFieldType. + * + * Note that GDT_UInt32 is mapped to OFTInteger64 to avoid data losses. + * GDT_UInt64 is mapped to OFTReal, which can be lossy. If values are + * guaranteed to be in [0, INT64_MAX] range, callers might want to use + * OFTInteger64 instead. + * + * @since 3.11 + */ +inline OGRFieldType GetOGRFieldType(const GDALExtendedDataType &oEDT) +{ + if (oEDT.GetClass() == GEDTC_NUMERIC) + return GetOGRFieldType(oEDT.GetNumericDataType()); + else if (oEDT.GetClass() == GEDTC_STRING) + return OFTString; + return OFTMaxType; +} + +} // namespace gdal + +#endif // GDAL_TYPETRAITS_H_INCLUDED diff --git a/ogr/ogrsf_frmts/filegdb/FGdbUtils.cpp b/ogr/ogrsf_frmts/filegdb/FGdbUtils.cpp index 3827e578a893..75e348747d3f 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbUtils.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbUtils.cpp @@ -18,6 +18,7 @@ #include "ogr_api.h" #include "ogrpgeogeometry.h" +#include "filegdb_reserved_keywords.h" using std::string; @@ -531,21 +532,11 @@ std::wstring FGDBEscapeReservedKeywords(const std::wstring &name) std::string newName = WStringToString(name); std::string upperName = CPLString(newName).toupper(); - // From ESRI docs - static const char *const RESERVED_WORDS[] = { - FGDB_OID_NAME, "ADD", "ALTER", "AND", "AS", "ASC", - "BETWEEN", "BY", "COLUMN", "CREATE", "DATE", "DELETE", - "DESC", "DROP", "EXISTS", "FOR", "FROM", "IN", - "INSERT", "INTO", "IS", "LIKE", "NOT", "NULL", - "OR", "ORDER", "SELECT", "SET", "TABLE", "UPDATE", - "VALUES", "WHERE", nullptr}; - // Append an underscore to any FGDB reserved words used as field names // This is the same behavior ArcCatalog follows. - for (int i = 0; RESERVED_WORDS[i] != nullptr; i++) + for (const char *pszKeyword : apszRESERVED_WORDS) { - const char *w = RESERVED_WORDS[i]; - if (upperName == w) + if (upperName == pszKeyword) { newName += '_'; break; diff --git a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp index 686e2aaeae36..dae75364e787 100644 --- a/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp +++ b/ogr/ogrsf_frmts/generic/ogrlayerarrow.cpp @@ -1780,17 +1780,8 @@ FillDateTimeArray(struct ArrowArray *psChild, auto nVal = CPLYMDHMSToUnixTime(&brokenDown) * 1000 + (static_cast(psRawField->Date.Second * 1000 + 0.5) % 1000); - if (nFieldTZFlag > OGR_TZFLAG_MIXED_TZ && + if (nFieldTZFlag >= OGR_TZFLAG_MIXED_TZ && psRawField->Date.TZFlag > OGR_TZFLAG_MIXED_TZ) - { - // Convert for psRawField->Date.TZFlag to nFieldTZFlag - const int TZOffset = - (psRawField->Date.TZFlag - nFieldTZFlag) * 15; - const int TZOffsetMS = TZOffset * 60 * 1000; - nVal -= TZOffsetMS; - } - else if (nFieldTZFlag == OGR_TZFLAG_MIXED_TZ && - psRawField->Date.TZFlag > OGR_TZFLAG_MIXED_TZ) { // Convert for psRawField->Date.TZFlag to UTC const int TZOffset = diff --git a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp index 1dd598648f96..f8ad1f0584c9 100644 --- a/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp +++ b/ogr/ogrsf_frmts/gml/ogrgmldatasource.cpp @@ -1069,12 +1069,29 @@ bool OGRGMLDataSource::Open(GDALOpenInfo *poOpenInfo) papszTypeNames = CSLTokenizeString2(osTypeName, ",", 0); + // Old non-documented way + const char *pszGML_DOWNLOAD_WFS_SCHEMA = + CPLGetConfigOption("GML_DOWNLOAD_WFS_SCHEMA", + nullptr); + if (pszGML_DOWNLOAD_WFS_SCHEMA) + { + CPLError( + CE_Warning, CPLE_AppDefined, + "Configuration option " + "GML_DOWNLOAD_WFS_SCHEMA is deprecated. " + "Please use GML_DOWNLOAD_SCHEMA instead of " + "the DOWNLOAD_SCHEMA open option"); + } + else + { + pszGML_DOWNLOAD_WFS_SCHEMA = "YES"; + } if (!bHasFoundXSD && CPLHTTPEnabled() && - CPLFetchBool( - poOpenInfo->papszOpenOptions, - "DOWNLOAD_SCHEMA", - CPLTestBool(CPLGetConfigOption( - "GML_DOWNLOAD_WFS_SCHEMA", "YES")))) + CPLFetchBool(poOpenInfo->papszOpenOptions, + "DOWNLOAD_SCHEMA", + CPLTestBool(CPLGetConfigOption( + "GML_DOWNLOAD_SCHEMA", + pszGML_DOWNLOAD_WFS_SCHEMA)))) { CPLHTTPResult *psResult = CPLHTTPFetch(pszEscapedURL, nullptr); diff --git a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h index 8628cb18dee6..b42cf82da80e 100644 --- a/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h +++ b/ogr/ogrsf_frmts/gpkg/ogr_geopackage.h @@ -372,8 +372,9 @@ class GDALGeoPackageDataset final : public OGRSQLiteBaseDataSource, int GetSrsId(const OGRSpatialReference *poSRS); const char *GetSrsName(const OGRSpatialReference &oSRS); - OGRSpatialReference *GetSpatialRef(int iSrsId, bool bFallbackToEPSG = false, - bool bEmitErrorIfNotFound = true); + std::unique_ptr + GetSpatialRef(int iSrsId, bool bFallbackToEPSG = false, + bool bEmitErrorIfNotFound = true); OGRErr CreateExtensionsTableIfNecessary(); bool HasExtensionsTable(); diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp index 0b0e5b5532b0..f40b1aabd98c 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagedatasource.cpp @@ -235,7 +235,7 @@ static OGRErr GDALGPKGImportFromEPSG(OGRSpatialReference *poSpatialRef, return eErr; } -OGRSpatialReference * +std::unique_ptr GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG, bool bEmitErrorIfNotFound) { @@ -245,7 +245,8 @@ GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG, if (oIter->second == nullptr) return nullptr; oIter->second->Reference(); - return oIter->second; + return std::unique_ptr(oIter->second); } if (iSrsId == 0 || iSrsId == -1) @@ -268,7 +269,8 @@ GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG, m_oMapSrsIdToSrs[iSrsId] = poSpatialRef; poSpatialRef->Reference(); - return poSpatialRef; + return std::unique_ptr(poSpatialRef); } CPLString oSQL; @@ -292,7 +294,8 @@ GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG, if (poSRS->importFromEPSG(iSrsId) == OGRERR_NONE) { poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); - return poSRS; + return std::unique_ptr(poSRS); } poSRS->Release(); } @@ -349,7 +352,8 @@ GDALGeoPackageDataset::GetSpatialRef(int iSrsId, bool bFallbackToEPSG, poSpatialRef->SetCoordinateEpoch(dfCoordinateEpoch); m_oMapSrsIdToSrs[iSrsId] = poSpatialRef; poSpatialRef->Reference(); - return poSpatialRef; + return std::unique_ptr( + poSpatialRef); } const char *GDALGeoPackageDataset::GetSrsName(const OGRSpatialReference &oSRS) @@ -679,10 +683,8 @@ int GDALGeoPackageDataset::GetSrsId(const OGRSpatialReference *poSRSIn) auto poRefSRS = GetSpatialRef(nSRSId); bool bOK = (poRefSRS == nullptr || - poSRS->IsSame(poRefSRS, apszIsSameOptions) || + poSRS->IsSame(poRefSRS.get(), apszIsSameOptions) || !CPLTestBool(CPLGetConfigOption("OGR_GPKG_CHECK_SRS", "YES"))); - if (poRefSRS) - poRefSRS->Release(); if (bOK) { return nSRSId; @@ -2858,11 +2860,9 @@ bool GDALGeoPackageDataset::OpenRaster( m_bRecordInsertedInGPKGContent = true; m_nSRID = nSRSId; - OGRSpatialReference *poSRS = GetSpatialRef(nSRSId); - if (poSRS) + if (auto poSRS = GetSpatialRef(nSRSId)) { - m_oSRS = *poSRS; - poSRS->Release(); + m_oSRS = *(poSRS.get()); } /* Various sanity checks added in the SELECT */ @@ -8704,7 +8704,8 @@ static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc, GDALGeoPackageDataset *poDS = static_cast(sqlite3_user_data(pContext)); - OGRSpatialReference *poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true); + std::unique_ptr poSrcSRS( + poDS->GetSpatialRef(sHeader.iSrsId, true)); if (poSrcSRS == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, @@ -8729,7 +8730,7 @@ static void OGRGeoPackageGeodesicArea(sqlite3_context *pContext, int argc, poGeom.reset(poGeomSpatialite); } - poGeom->assignSpatialReference(poSrcSRS); + poGeom->assignSpatialReference(poSrcSRS.get()); sqlite3_result_double( pContext, OGR_G_GeodesicArea(OGRGeometry::ToHandle(poGeom.get()))); } @@ -8767,7 +8768,7 @@ static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext, GDALGeoPackageDataset *poDS = static_cast(sqlite3_user_data(pContext)); - OGRSpatialReference *poSrcSRS = nullptr; + std::unique_ptr poSrcSRS; if (argc == 2) { poSrcSRS = poDS->GetSpatialRef(sHeader.iSrsId, true); @@ -8797,7 +8798,7 @@ static void OGRGeoPackageLengthOrGeodesicLength(sqlite3_context *pContext, } if (argc == 2) - poGeom->assignSpatialReference(poSrcSRS); + poGeom->assignSpatialReference(poSrcSRS.get()); sqlite3_result_double( pContext, @@ -8853,8 +8854,8 @@ void OGRGeoPackageTransform(sqlite3_context *pContext, int argc, } else { - OGRSpatialReference *poSrcSRS = - poDS->GetSpatialRef(sHeader.iSrsId, true); + std::unique_ptr + poSrcSRS(poDS->GetSpatialRef(sHeader.iSrsId, true)); if (poSrcSRS == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, @@ -8863,19 +8864,17 @@ void OGRGeoPackageTransform(sqlite3_context *pContext, int argc, return; } - OGRSpatialReference *poDstSRS = poDS->GetSpatialRef(nDestSRID, true); + std::unique_ptr + poDstSRS(poDS->GetSpatialRef(nDestSRID, true)); if (poDstSRS == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, "Target SRID (%d) is invalid", nDestSRID); sqlite3_result_blob(pContext, nullptr, 0, nullptr); - poSrcSRS->Release(); return; } - poCT = OGRCreateCoordinateTransformation(poSrcSRS, poDstSRS); - poSrcSRS->Release(); - poDstSRS->Release(); - + poCT = + OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get()); if (poCT == nullptr) { sqlite3_result_blob(pContext, nullptr, 0, nullptr); diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagelayer.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagelayer.cpp index 0e2ae1a5aa24..479b717edc81 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagelayer.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagelayer.cpp @@ -1144,12 +1144,10 @@ void OGRGeoPackageLayer::BuildFeatureDefn(const char *pszLayerName, wkbUnknown); /* Read the SRS */ - OGRSpatialReference *poSRS = - m_poDS->GetSpatialRef(nSRID, true); + auto poSRS = m_poDS->GetSpatialRef(nSRID, true); if (poSRS) { - oGeomField.SetSpatialRef(poSRS); - poSRS->Dereference(); + oGeomField.SetSpatialRef(poSRS.get()); } OGRwkbGeometryType eGeomType = poGeom->getGeometryType(); diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp index ced8b3ba1eb5..6acc0ff19f49 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp @@ -1202,12 +1202,11 @@ OGRErr OGRGeoPackageTableLayer::ReadTableDefinition() FALSE); /* Read the SRS */ - OGRSpatialReference *poSRS = m_poDS->GetSpatialRef(m_iSrs); + auto poSRS = m_poDS->GetSpatialRef(m_iSrs); if (poSRS) { m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef( - poSRS); - poSRS->Dereference(); + poSRS.get()); } } else if (!STARTS_WITH( @@ -1729,6 +1728,24 @@ OGRErr OGRGeoPackageTableLayer::CreateField(const OGRFieldDefn *poField, GDALGeoPackageDataset::LaunderName(oFieldDefn.GetNameRef()) .c_str()); + if (m_poFeatureDefn->GetFieldIndex(oFieldDefn.GetNameRef()) >= 0) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Cannot create field %s. " + "A field with the same name already exists.", + oFieldDefn.GetNameRef()); + return OGRERR_FAILURE; + } + + if (m_poFeatureDefn->GetGeomFieldIndex(oFieldDefn.GetNameRef()) >= 0) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Cannot create field %s. " + "It has the same name as the geometry field.", + oFieldDefn.GetNameRef()); + return OGRERR_FAILURE; + } + if (m_pszFidColumn != nullptr && EQUAL(oFieldDefn.GetNameRef(), m_pszFidColumn) && poField->GetType() != OFTInteger && @@ -1743,6 +1760,19 @@ OGRErr OGRGeoPackageTableLayer::CreateField(const OGRFieldDefn *poField, return OGRERR_FAILURE; } + const int nMaxColumns = + sqlite3_limit(m_poDS->GetDB(), SQLITE_LIMIT_COLUMN, -1); + // + 1 for the FID column + if (m_poFeatureDefn->GetFieldCount() + + m_poFeatureDefn->GetGeomFieldCount() + 1 >= + nMaxColumns) + { + CPLError(CE_Failure, CPLE_AppDefined, + "Cannot add field %s. Limit of %d columns reached", + oFieldDefn.GetNameRef(), nMaxColumns); + return OGRERR_FAILURE; + } + if (!m_bDeferredCreation) { CPLString osCommand; @@ -5660,8 +5690,7 @@ void OGRGeoPackageTableLayer::SetCreationParameters( /* bEmitErrorIfNotFound = */ false); if (poGotSRS) { - oGeomFieldDefn.SetSpatialRef(poGotSRS); - poGotSRS->Release(); + oGeomFieldDefn.SetSpatialRef(poGotSRS.get()); } else { diff --git a/ogr/ogrsf_frmts/miramon/mm_wrlayr.c b/ogr/ogrsf_frmts/miramon/mm_wrlayr.c index 976adb4d1589..b47e8163f5f2 100644 --- a/ogr/ogrsf_frmts/miramon/mm_wrlayr.c +++ b/ogr/ogrsf_frmts/miramon/mm_wrlayr.c @@ -6333,8 +6333,9 @@ int MMCreateMMDB(struct MiraMonVectLayerInfo *hMiraMonLayer, // Before allocating new memory, there might be some previously allocated but unused memory. // Let's free that memory first. - if (hMiraMonLayer->MMArc.MMNode.MMAdmDB.pMMBDXP) - MM_ReleaseDBFHeader(&hMiraMonLayer->MMArc.MMNode.MMAdmDB.pMMBDXP); + if (hMiraMonLayer->MMPolygon.MMArc.MMAdmDB.pMMBDXP) + MM_ReleaseDBFHeader( + &hMiraMonLayer->MMPolygon.MMArc.MMAdmDB.pMMBDXP); pBD_XP_Aux = hMiraMonLayer->MMPolygon.MMArc.MMAdmDB.pMMBDXP = MM_CreateDBFHeader(5, hMiraMonLayer->nCharSet); @@ -6349,6 +6350,12 @@ int MMCreateMMDB(struct MiraMonVectLayerInfo *hMiraMonLayer, : 9)) return 1; + // Before allocating new memory, there might be some previously allocated but unused memory. + // Let's free that memory first. + if (hMiraMonLayer->MMPolygon.MMArc.MMNode.MMAdmDB.pMMBDXP) + MM_ReleaseDBFHeader( + &hMiraMonLayer->MMPolygon.MMArc.MMNode.MMAdmDB.pMMBDXP); + pBD_XP_Aux = hMiraMonLayer->MMPolygon.MMArc.MMNode.MMAdmDB.pMMBDXP = MM_CreateDBFHeader(3, hMiraMonLayer->nCharSet); diff --git a/ogr/ogrsf_frmts/oci/ogrocisession.cpp b/ogr/ogrsf_frmts/oci/ogrocisession.cpp index 56776a3e748b..3e8950bf4592 100644 --- a/ogr/ogrsf_frmts/oci/ogrocisession.cpp +++ b/ogr/ogrsf_frmts/oci/ogrocisession.cpp @@ -318,9 +318,9 @@ int OGROCISession::EstablishSession(const char *pszUseridIn, OGROCIStatement oSetNLSTimeFormat(this); if (oSetNLSTimeFormat.Execute( "ALTER SESSION SET NLS_DATE_FORMAT='YYYY/MM/DD' \ - NLS_TIME_FORMAT='HH24:MI:SS' NLS_TIME_TZ_FORMAT='HH24:MI:SS TZHTZM' \ - NLS_TIMESTAMP_FORMAT='YYYY/MM/DD HH24:MI:SS' \ - NLS_TIMESTAMP_TZ_FORMAT='YYYY/MM/DD HH24:MI:SS TZHTZM' \ + NLS_TIME_FORMAT='HH24:MI:SS' NLS_TIME_TZ_FORMAT='HH24:MI:SS.FF TZHTZM' \ + NLS_TIMESTAMP_FORMAT='YYYY/MM/DD HH24:MI:SS.FF' \ + NLS_TIMESTAMP_TZ_FORMAT='YYYY/MM/DD HH24:MI:SS.FF TZHTZM' \ NLS_NUMERIC_CHARACTERS = '. '") != CE_None) return OGRERR_FAILURE; diff --git a/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp b/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp index 5eb92a5e4582..de9bb30d5c71 100644 --- a/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp +++ b/ogr/ogrsf_frmts/oci/ogrociwritablelayer.cpp @@ -291,7 +291,7 @@ OGRErr OGROCIWritableLayer::CreateField(const OGRFieldDefn *poFieldIn, } else if (oField.GetType() == OFTDateTime) { - snprintf(szFieldType, sizeof(szFieldType), "TIMESTAMP"); + snprintf(szFieldType, sizeof(szFieldType), "TIMESTAMP(3)"); } else if (bApproxOK) { diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdb_reserved_keywords.h b/ogr/ogrsf_frmts/openfilegdb/filegdb_reserved_keywords.h new file mode 100644 index 000000000000..1a006da3356d --- /dev/null +++ b/ogr/ogrsf_frmts/openfilegdb/filegdb_reserved_keywords.h @@ -0,0 +1,24 @@ +/****************************************************************************** + * + * Project: OpenGIS Simple Features Reference Implementation + * Purpose: List of reserver keywords for File Geodatabase + * Author: Even Rouault, + * + ****************************************************************************** + * Copyright (c) 2024, Even Rouault, + * + * SPDX-License-Identifier: MIT + ****************************************************************************/ + +#ifndef FILEGDB_RESERVED_KEYWORDS_H +#define FILEGDB_RESERVED_KEYWORDS_H + +// from https://support.esri.com/en-us/knowledge-base/what-are-the-reserved-words-for-esri-s-file-geodatabase-000010906 +static constexpr const char *const apszRESERVED_WORDS[] = { + "ADD", "ALTER", "AND", "BETWEEN", "BY", "COLUMN", "CREATE", + "DELETE", "DROP", "EXISTS", "FOR", "FROM", "GROUP", "IN", + "INSERT", "INTO", "IS", "LIKE", "NOT", "NULL", "OR", + "ORDER", "SELECT", "SET", "TABLE", "UPDATE", "VALUES", "WHERE", +}; + +#endif // FILEGDB_RESERVED_KEYWORDS_H diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp index 8b487c83864b..78f7399ca8e7 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp @@ -40,6 +40,7 @@ #include "filegdbtable.h" #include "filegdbtable_priv.h" #include "filegdb_coordprec_write.h" +#include "filegdb_reserved_keywords.h" /*************************************************************************/ /* StringToWString() */ @@ -137,20 +138,11 @@ static std::wstring EscapeReservedKeywords(const std::wstring &name) std::string newName = WStringToString(name); std::string upperName = CPLString(newName).toupper(); - // From ESRI docs - static const char *const RESERVED_WORDS[] = { - "OBJECTID", "ADD", "ALTER", "AND", "AS", "ASC", "BETWEEN", - "BY", "COLUMN", "CREATE", "DATE", "DELETE", "DESC", "DROP", - "EXISTS", "FOR", "FROM", "IN", "INSERT", "INTO", "IS", - "LIKE", "NOT", "NULL", "OR", "ORDER", "SELECT", "SET", - "TABLE", "UPDATE", "VALUES", "WHERE", nullptr}; - // Append an underscore to any FGDB reserved words used as field names // This is the same behavior ArcCatalog follows. - for (int i = 0; RESERVED_WORDS[i] != nullptr; i++) + for (const char *pszKeyword : apszRESERVED_WORDS) { - const char *w = RESERVED_WORDS[i]; - if (upperName == w) + if (upperName == pszKeyword) { newName += '_'; break; diff --git a/ogr/ogrsf_frmts/xodr/ogr_xodr.h b/ogr/ogrsf_frmts/xodr/ogr_xodr.h index 380f717487c3..31fbe94adf73 100644 --- a/ogr/ogrsf_frmts/xodr/ogr_xodr.h +++ b/ogr/ogrsf_frmts/xodr/ogr_xodr.h @@ -62,7 +62,7 @@ class OGRXODRLayer : public OGRLayer protected: RoadElements m_roadElements{}; bool m_bDissolveTIN{false}; - OGRSpatialReference m_poSRS{}; + OGRSpatialReference m_oSRS{}; /* Unique feature ID which is automatically incremented for any new road feature creation. */ int m_nNextFID{0}; diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp index 38f758b4e7f7..a8abf37c4a4f 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrdrivercore.cpp @@ -20,7 +20,9 @@ int OGRXODRDriverIdentify(GDALOpenInfo *poOpenInfo) { - return EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "xodr"); + return poOpenInfo->fpL != nullptr && + EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "xodr") && + !STARTS_WITH(poOpenInfo->pszFilename, "/vsi"); } /************************************************************************/ diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp index 88183652e956..db69f4934eb4 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayer.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of OGRXODRLayer. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -34,7 +34,10 @@ OGRXODRLayer::OGRXODRLayer(const RoadElements &xodrRoadElements, : m_roadElements(xodrRoadElements), m_bDissolveTIN(dissolveTriangulatedSurface) { - m_poSRS.importFromProj4(proj4Defn.c_str()); + if (!proj4Defn.empty()) + { + m_oSRS.importFromProj4(proj4Defn.c_str()); + } resetRoadElementIterators(); } @@ -96,4 +99,4 @@ OGRXODRLayer::triangulateSurface(const odr::Mesh3D &mesh) } return tin; -} \ No newline at end of file +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp index 5f76eb5b0a03..c0cc156b75bc 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlane.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of Lane layer. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -34,7 +34,10 @@ OGRXODRLayerLane::OGRXODRLayerLane(const RoadElements &xodrRoadElements, { m_poFeatureDefn->SetGeomType(wkbTINZ); } - m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + if (!m_oSRS.IsEmpty()) + { + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_oSRS); + } OGRFieldDefn oFieldLaneID("LaneID", OFTInteger); m_poFeatureDefn->AddFieldDefn(&oFieldLaneID); @@ -91,7 +94,8 @@ OGRFeature *OGRXODRLayerLane::GetNextRawFeature() OGRGeometry *dissolvedPolygon = tin->UnaryUnion(); if (dissolvedPolygon != nullptr) { - dissolvedPolygon->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + dissolvedPolygon->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(dissolvedPolygon); } else @@ -104,7 +108,8 @@ OGRFeature *OGRXODRLayerLane::GetNextRawFeature() } else { - tin->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + tin->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(tin.release()); } @@ -134,4 +139,4 @@ OGRFeature *OGRXODRLayerLane::GetNextRawFeature() // End of features for the given layer reached. return nullptr; } -} \ No newline at end of file +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp index 5a60e3093911..fe0dd43875b7 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerlaneborder.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of LaneBorder layer. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -26,7 +26,8 @@ OGRXODRLayerLaneBorder::OGRXODRLayerLaneBorder( OGRwkbGeometryType wkbLineStringWithZ = OGR_GT_SetZ(wkbLineString); m_poFeatureDefn->SetGeomType(wkbLineStringWithZ); - m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + if (!m_oSRS.IsEmpty()) + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_oSRS); OGRFieldDefn oFieldID("ID", OFTInteger); m_poFeatureDefn->AddFieldDefn(&oFieldID); @@ -73,7 +74,8 @@ OGRFeature *OGRXODRLayerLaneBorder::GetNextRawFeature() lineString->addPoint(borderVertex[0], borderVertex[1], borderVertex[2]); } - lineString->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + lineString->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(lineString.release()); // Populate other fields @@ -103,4 +105,4 @@ OGRFeature *OGRXODRLayerLaneBorder::GetNextRawFeature() // End of features for the given layer reached. return nullptr; } -} \ No newline at end of file +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp index 7e16f96b8c0e..02bc05fa63d3 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerreferenceline.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of ReferenceLine layer. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -26,7 +26,8 @@ OGRXODRLayerReferenceLine::OGRXODRLayerReferenceLine( OGRwkbGeometryType wkbLineStringWithZ = OGR_GT_SetZ(wkbLineString); m_poFeatureDefn->SetGeomType(wkbLineStringWithZ); - m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + if (!m_oSRS.IsEmpty()) + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_oSRS); OGRFieldDefn oFieldID("ID", OFTString); m_poFeatureDefn->AddFieldDefn(&oFieldID); @@ -65,7 +66,8 @@ OGRFeature *OGRXODRLayerReferenceLine::GetNextRawFeature() { lineString->addPoint(lineVertex[0], lineVertex[1], lineVertex[2]); } - lineString->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + lineString->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(lineString.release()); // Populate other fields @@ -87,4 +89,4 @@ OGRFeature *OGRXODRLayerReferenceLine::GetNextRawFeature() // End of features for the given layer reached. return nullptr; } -} \ No newline at end of file +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp index f10d2f81e2d8..2c49a1487913 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadmark.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of RoadMark layer. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -34,7 +34,8 @@ OGRXODRLayerRoadMark::OGRXODRLayerRoadMark( { m_poFeatureDefn->SetGeomType(wkbTINZ); } - m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + if (!m_oSRS.IsEmpty()) + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_oSRS); OGRFieldDefn oFieldRoadID("RoadID", OFTString); m_poFeatureDefn->AddFieldDefn(&oFieldRoadID); @@ -75,7 +76,8 @@ OGRFeature *OGRXODRLayerRoadMark::GetNextRawFeature() OGRGeometry *dissolvedPolygon = tin->UnaryUnion(); if (dissolvedPolygon != nullptr) { - dissolvedPolygon->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + dissolvedPolygon->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(dissolvedPolygon); } else @@ -88,7 +90,8 @@ OGRFeature *OGRXODRLayerRoadMark::GetNextRawFeature() } else { - tin->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + tin->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(tin.release()); } @@ -114,4 +117,4 @@ OGRFeature *OGRXODRLayerRoadMark::GetNextRawFeature() // End of features for the given layer reached. return nullptr; } -} \ No newline at end of file +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp index 199c69accf82..65c9ccef9b67 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadobject.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of RoadObject layer. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -25,7 +25,8 @@ OGRXODRLayerRoadObject::OGRXODRLayerRoadObject( SetDescription(FEATURE_CLASS_NAME.c_str()); m_poFeatureDefn->SetGeomType(wkbTINZ); - m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + if (!m_oSRS.IsEmpty()) + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_oSRS); OGRFieldDefn oFieldObjectID("ObjectID", OFTString); m_poFeatureDefn->AddFieldDefn(&oFieldObjectID); @@ -65,7 +66,8 @@ OGRFeature *OGRXODRLayerRoadObject::GetNextRawFeature() // In contrast to other XODR layers, dissolving of RoadObject TINs is not an option, because faces of "true" 3D objects might collapse. std::unique_ptr tin = triangulateSurface(roadObjectMesh); - tin->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + tin->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(tin.release()); // Populate other fields @@ -92,4 +94,4 @@ OGRFeature *OGRXODRLayerRoadObject::GetNextRawFeature() // End of features for the given layer reached. return nullptr; } -} \ No newline at end of file +} diff --git a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp index 3171ae2b60c8..2a6acced8a8f 100644 --- a/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp +++ b/ogr/ogrsf_frmts/xodr/ogrxodrlayerroadsignal.cpp @@ -3,7 +3,7 @@ * Project: OpenGIS Simple Features for OpenDRIVE * Purpose: Implementation of RoadSignal layer. * Author: Michael Scholz, German Aerospace Center (DLR) - * Gülsen Bardak, German Aerospace Center (DLR) + * Gülsen Bardak, German Aerospace Center (DLR) * ****************************************************************************** * Copyright 2024 German Aerospace Center (DLR), Institute of Transportation Systems @@ -34,7 +34,8 @@ OGRXODRLayerRoadSignal::OGRXODRLayerRoadSignal( { m_poFeatureDefn->SetGeomType(wkbTINZ); } - m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_poSRS); + if (!m_oSRS.IsEmpty()) + m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(&m_oSRS); OGRFieldDefn oFieldSignalID("SignalID", OFTString); m_poFeatureDefn->AddFieldDefn(&oFieldSignalID); @@ -102,14 +103,16 @@ OGRFeature *OGRXODRLayerRoadSignal::GetNextRawFeature() odr::Vec3D xyz = road.get_xyz(s, t, h); auto point = std::make_unique(xyz[0], xyz[1], xyz[2]); - point->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + point->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(point.release()); } else { std::unique_ptr tin = triangulateSurface(roadSignalMesh); - tin->assignSpatialReference(&m_poSRS); + if (!m_oSRS.IsEmpty()) + tin->assignSpatialReference(&m_oSRS); feature->SetGeometryDirectly(tin.release()); } @@ -149,4 +152,4 @@ OGRFeature *OGRXODRLayerRoadSignal::GetNextRawFeature() // End of features for the given layer reached. return nullptr; } -} \ No newline at end of file +} diff --git a/scripts/gdal-bash-completion.sh b/scripts/gdal-bash-completion.sh index 6bc208bcbd02..e1edd31039fa 100644 --- a/scripts/gdal-bash-completion.sh +++ b/scripts/gdal-bash-completion.sh @@ -75,7 +75,7 @@ _gdalbuildvrt() _get_comp_words_by_ref cur prev case "$cur" in -*) - key_list="--help --long-usage --help-general --quiet -strict -non_strict -tile_index -resolution -tr -input_file_list -separate -allow_projection_difference -sd -tap -te -addalpha -b -hidenodata -overwrite -srcnodata -vrtnodata -a_srs -r -oo -ignore_srcmaskband -nodata_max_mask_threshold --version --build --license --formats --format --optfile --config --debug --pause --locale " + key_list="--help --long-usage --help-general --quiet -strict -non_strict -tile_index -resolution -tr -input_file_list -separate -allow_projection_difference -sd -tap -te -addalpha -b -hidenodata -overwrite -srcnodata -vrtnodata -a_srs -r -oo -co -ignore_srcmaskband -nodata_max_mask_threshold --version --build --license --formats --format --optfile --config --debug --pause --locale " mapfile -t COMPREPLY < <(compgen -W "$key_list" -- "$cur") return 0 ;; @@ -490,7 +490,7 @@ _gdal_rasterize() _get_comp_words_by_ref cur prev case "$cur" in -*) - key_list="--help --long-usage --help-general -b -i -at -burn -a -3d -add -l -sql -where -dialect -a_nodata -init -a_srs -to -te -tr -ts -tap -optim -co -ot -of -oo --quiet --version --build --license --formats --format --optfile --config --debug --pause --locale " + key_list="--help --long-usage --help-general -b -i -at -burn -a -3d -add -l -sql -where -dialect -a_nodata -init -a_srs -to -te -tr -ts -tap -optim -co -ot -of --quiet -oo --version --build --license --formats --format --optfile --config --debug --pause --locale " mapfile -t COMPREPLY < <(compgen -W "$key_list" -- "$cur") return 0 ;; diff --git a/swig/include/python/gdal_python.i b/swig/include/python/gdal_python.i index 902aa5d6bf50..9fef57d021e5 100644 --- a/swig/include/python/gdal_python.i +++ b/swig/include/python/gdal_python.i @@ -2485,6 +2485,17 @@ mapGRIORAMethodToString = { gdalconst.GRIORA_Gauss: 'gauss', } +def _addCreationOptions(new_options, creationOptions): + """Update new_options with creationOptions formatted as expected by utilities""" + if isinstance(creationOptions, str): + new_options += ['-co', creationOptions] + elif isinstance(creationOptions, dict): + for k, v in creationOptions.items(): + new_options += ['-co', f'{k}={v}'] + else: + for opt in creationOptions: + new_options += ['-co', opt] + def TranslateOptions(options=None, format=None, outputType = gdalconst.GDT_Unknown, bandList=None, maskBand=None, width = 0, height = 0, widthPct = 0.0, heightPct = 0.0, @@ -2599,16 +2610,9 @@ def TranslateOptions(options=None, format=None, if width != 0 or height != 0: new_options += ['-outsize', str(width), str(height)] elif widthPct != 0 and heightPct != 0: - new_options += ['-outsize', str(widthPct) + '%%', str(heightPct) + '%%'] + new_options += ['-outsize', str(widthPct) + '%', str(heightPct) + '%'] if creationOptions is not None: - if isinstance(creationOptions, str): - new_options += ['-co', creationOptions] - elif isinstance(creationOptions, dict): - for k, v in creationOptions.items(): - new_options += ['-co', f'{k}={v}'] - else: - for opt in creationOptions: - new_options += ['-co', opt] + _addCreationOptions(new_options, creationOptions) if srcWin is not None: new_options += ['-srcwin', _strHighPrec(srcWin[0]), _strHighPrec(srcWin[1]), _strHighPrec(srcWin[2]), _strHighPrec(srcWin[3])] if strict: @@ -2929,12 +2933,7 @@ def WarpOptions(options=None, format=None, if warpMemoryLimit is not None: new_options += ['-wm', str(warpMemoryLimit)] if creationOptions is not None: - if isinstance(creationOptions, dict): - for k, v in creationOptions.items(): - new_options += ['-co', f'{k}={v}'] - else: - for opt in creationOptions: - new_options += ['-co', opt] + _addCreationOptions(new_options, creationOptions) if srcNodata is not None: new_options += ['-srcnodata', str(srcNodata)] if dstNodata is not None: @@ -3528,12 +3527,7 @@ def DEMProcessingOptions(options=None, colorFilename=None, format=None, if format is not None: new_options += ['-of', format] if creationOptions is not None: - if isinstance(creationOptions, dict): - for k, v in creationOptions.items(): - new_options += ['-co', f'{k}={v}'] - else: - for opt in creationOptions: - new_options += ['-co', opt] + _addCreationOptions(new_options, creationOptions) if computeEdges: new_options += ['-compute_edges'] if alg: @@ -3661,12 +3655,7 @@ def NearblackOptions(options=None, format=None, if format is not None: new_options += ['-of', format] if creationOptions is not None: - if isinstance(creationOptions, dict): - for k, v in creationOptions.items(): - new_options += ['-co', f'{k}={v}'] - else: - for opt in creationOptions: - new_options += ['-co', opt] + _addCreationOptions(new_options, creationOptions) if white: new_options += ['-white'] if colors is not None: @@ -3816,12 +3805,7 @@ def GridOptions(options=None, format=None, if width != 0 or height != 0: new_options += ['-outsize', str(width), str(height)] if creationOptions is not None: - if isinstance(creationOptions, dict): - for k, v in creationOptions.items(): - new_options += ['-co', f'{k}={v}'] - else: - for opt in creationOptions: - new_options += ['-co', opt] + _addCreationOptions(new_options, creationOptions) if outputBounds is not None: new_options += ['-txe', _strHighPrec(outputBounds[0]), _strHighPrec(outputBounds[2]), '-tye', _strHighPrec(outputBounds[1]), _strHighPrec(outputBounds[3])] if outputSRS is not None: @@ -3982,12 +3966,7 @@ def RasterizeOptions(options=None, format=None, if outputType != gdalconst.GDT_Unknown: new_options += ['-ot', GetDataTypeName(outputType)] if creationOptions is not None: - if isinstance(creationOptions, dict): - for k, v in creationOptions.items(): - new_options += ['-co', f'{k}={v}'] - else: - for opt in creationOptions: - new_options += ['-co', opt] + _addCreationOptions(new_options, creationOptions) if bands is not None: for b in bands: new_options += ['-b', str(b)] @@ -4345,6 +4324,7 @@ def BuildVRTOptions(options=None, hideNodata=None, nodataMaxMaskThreshold=None, strict=False, + creationOptions=None, callback=None, callback_data=None): """Create a BuildVRTOptions() object that can be passed to gdal.BuildVRT() @@ -4385,6 +4365,8 @@ def BuildVRTOptions(options=None, value of the mask band of a source below which the source band values should be replaced by VRTNodata (or 0 if not specified) strict: set to True if warnings should be failures + creationOptions: + list or dict of creation options callback: callback method. callback_data: @@ -4437,6 +4419,8 @@ def BuildVRTOptions(options=None, new_options += ['-hidenodata'] if strict: new_options += ['-strict'] + if creationOptions is not None: + _addCreationOptions(new_options, creationOptions) if return_option_list: return new_options @@ -4748,12 +4732,7 @@ def MultiDimTranslateOptions(options=None, format=None, creationOptions=None, if format is not None: new_options += ['-of', format] if creationOptions is not None: - if isinstance(creationOptions, dict): - for k, v in creationOptions.items(): - new_options += ['-co', f'{k}={v}'] - else: - for opt in creationOptions: - new_options += ['-co', opt] + _addCreationOptions(new_options, creationOptions) if arraySpecs is not None: for s in arraySpecs: new_options += ['-array', s] diff --git a/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py b/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py index c3e2ddd14dc3..df0f5f01c2db 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py +++ b/swig/python/gdal-utils/osgeo_utils/gdal2tiles.py @@ -707,8 +707,8 @@ class GDALError(Exception): def exit_with_error(message: str, details: str = "") -> NoReturn: # Message printing and exit code kept from the way it worked using the OptionParser (in case # someone parses the error output) - sys.stderr.write("Usage: gdal2tiles.py [options] input_file [output]\n\n") - sys.stderr.write("gdal2tiles.py: error: %s\n" % message) + sys.stderr.write("Usage: gdal2tiles [options] input_file [output]\n\n") + sys.stderr.write("gdal2tiles: error: %s\n" % message) if details: sys.stderr.write("\n\n%s\n" % details) diff --git a/swig/python/gdal-utils/osgeo_utils/gdal_merge.py b/swig/python/gdal-utils/osgeo_utils/gdal_merge.py index 1e005ea56b58..1ca222dcd7ae 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdal_merge.py +++ b/swig/python/gdal-utils/osgeo_utils/gdal_merge.py @@ -366,7 +366,7 @@ def copy_into(self, t_fh, s_band=1, t_band=1, nodata_arg=None, verbose=0): # ============================================================================= def Usage(isError): f = sys.stderr if isError else sys.stdout - print("Usage: gdal_merge.py [--help] [--help-general]", file=f) + print("Usage: gdal_merge [--help] [--help-general]", file=f) print( " [-o ] [-of ] [-co =]...", file=f, diff --git a/swig/python/gdal-utils/osgeo_utils/gdal_proximity.py b/swig/python/gdal-utils/osgeo_utils/gdal_proximity.py index 58d885976e8f..c7541633b5bd 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdal_proximity.py +++ b/swig/python/gdal-utils/osgeo_utils/gdal_proximity.py @@ -26,7 +26,7 @@ def Usage(isError): f = sys.stderr if isError else sys.stdout print( """ -Usage: gdal_proximity.py [--help] [--help-general] +Usage: gdal_proximity [--help] [--help-general] [-srcband ] [-dstband ] [-of ] [-co =]... [-ot {Byte|UInt16|UInt32|Float32|etc}] diff --git a/swig/python/gdal-utils/osgeo_utils/gdal_retile.py b/swig/python/gdal-utils/osgeo_utils/gdal_retile.py index 728104d5fe16..e16a800913a7 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdal_retile.py +++ b/swig/python/gdal-utils/osgeo_utils/gdal_retile.py @@ -949,7 +949,7 @@ def UsageFormat(): def Usage(isError): f = sys.stderr if isError else sys.stdout - print("Usage: gdal_retile.py [--help] [--help-general]", file=f) + print("Usage: gdal_retile [--help] [--help-general]", file=f) print(" [-v] [-q] [-co =]... [-of ]", file=f) print(" [-ps ]", file=f) print(" [-overlap ]", file=f) diff --git a/swig/python/gdal-utils/osgeo_utils/gdalattachpct.py b/swig/python/gdal-utils/osgeo_utils/gdalattachpct.py index 264c8b5ae29c..a44267341340 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdalattachpct.py +++ b/swig/python/gdal-utils/osgeo_utils/gdalattachpct.py @@ -30,7 +30,7 @@ def Usage(isError=True): f = sys.stderr if isError else sys.stdout - print("Usage: gdalattachpct.py [--help] [--help-general]", file=f) + print("Usage: gdalattachpct [--help] [--help-general]", file=f) print(" ", file=f) return 2 if isError else 0 diff --git a/swig/python/gdal-utils/osgeo_utils/gdalcompare.py b/swig/python/gdal-utils/osgeo_utils/gdalcompare.py index 52ddc4bb854a..32bacaccb090 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdalcompare.py +++ b/swig/python/gdal-utils/osgeo_utils/gdalcompare.py @@ -473,7 +473,7 @@ def find_diff( def Usage(isError=True): f = sys.stderr if isError else sys.stdout - print("Usage: gdalcompare.py [--help] [--help-general]", file=f) + print("Usage: gdalcompare [--help] [--help-general]", file=f) print(" [-dumpdiffs] [-skip_binary] [-skip_overviews]", file=f) print(" [-skip_geolocation] [-skip_geotransform]", file=f) print(" [-skip_metadata] [-skip_rpc] [-skip_srs]", file=f) diff --git a/swig/python/gdal-utils/osgeo_utils/gdalmove.py b/swig/python/gdal-utils/osgeo_utils/gdalmove.py index 64c109174633..a171fe3a11f8 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdalmove.py +++ b/swig/python/gdal-utils/osgeo_utils/gdalmove.py @@ -222,7 +222,7 @@ def move( def Usage(isError=True): f = sys.stderr if isError else sys.stdout print( - """Usage: gdalmove.py [--help] [--help-general] + """Usage: gdalmove [--help] [--help-general] [-s_srs ] -t_srs [-et ] """, file=f, diff --git a/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py b/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py index aecb2960dec1..fe9f900072a6 100644 --- a/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py +++ b/swig/python/gdal-utils/osgeo_utils/ogr_layer_algebra.py @@ -25,7 +25,7 @@ def Usage(isError): print( """ -Usage: ogr_layer_algebra.py [--help] [--help-general] +Usage: ogr_layer_algebra [--help] [--help-general] Union|Intersection|SymDifference|Identity|Update|Clip|Erase -input_ds [-input_lyr ] -method_ds [-method_lyr ] diff --git a/swig/python/gdal-utils/osgeo_utils/ogrmerge.py b/swig/python/gdal-utils/osgeo_utils/ogrmerge.py index 1bb04f45c183..98a4e3353e35 100644 --- a/swig/python/gdal-utils/osgeo_utils/ogrmerge.py +++ b/swig/python/gdal-utils/osgeo_utils/ogrmerge.py @@ -27,7 +27,7 @@ def Usage(isError): f = sys.stderr if isError else sys.stdout - print("Usage: ogrmerge.py [--help] [--help-general]", file=f) + print("Usage: ogrmerge [--help] [--help-general]", file=f) print(" -o []...", file=f) print(" [-f ] [-single] [-nln ]", file=f) print(" [-update | -overwrite_ds] [-append | -overwrite_layer]", file=f)