From 792ef4ce33139d78e744fddc85e810831c3db0be Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 14:44:12 +0100 Subject: [PATCH 01/50] ogr2ogr: use CPLError() to make sure password is redacted in error message (fixes #9318) --- apps/ogr2ogr_bin.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/ogr2ogr_bin.cpp b/apps/ogr2ogr_bin.cpp index 9558e220967f..18fc96b38e1b 100644 --- a/apps/ogr2ogr_bin.cpp +++ b/apps/ogr2ogr_bin.cpp @@ -386,10 +386,9 @@ MAIN_START(nArgc, papszArgv) { GDALDriverManager *poDM = GetGDALDriverManager(); - fprintf(stderr, - "FAILURE:\n" - "Unable to open datasource `%s' with the following drivers.\n", - psOptionsForBinary->osDataSource.c_str()); + CPLError(CE_Failure, CPLE_AppDefined, + "Unable to open datasource `%s' with the following drivers.", + psOptionsForBinary->osDataSource.c_str()); for (int iDriver = 0; iDriver < poDM->GetDriverCount(); iDriver++) { From 8f7256cfe47c2a5a50dd4dc6194cf7cdece908f4 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Mon, 26 Feb 2024 15:55:51 +0100 Subject: [PATCH 02/50] Internal shapelib: resync with upstream --- ogr/ogrsf_frmts/shape/dbfopen.c | 21 ++++---- ogr/ogrsf_frmts/shape/sbnsearch.c | 4 +- ogr/ogrsf_frmts/shape/shapefil.h | 66 +++++++++--------------- ogr/ogrsf_frmts/shape/shapefil_private.h | 23 +++++++++ ogr/ogrsf_frmts/shape/shpopen.c | 11 ++-- ogr/ogrsf_frmts/shape/shptree.c | 14 ++--- 6 files changed, 74 insertions(+), 65 deletions(-) diff --git a/ogr/ogrsf_frmts/shape/dbfopen.c b/ogr/ogrsf_frmts/shape/dbfopen.c index 15378ecd9ce0..84a048544aa0 100644 --- a/ogr/ogrsf_frmts/shape/dbfopen.c +++ b/ogr/ogrsf_frmts/shape/dbfopen.c @@ -1169,7 +1169,8 @@ static bool DBFIsValueNULL(char chType, const char *pszValue) /* Contributed by Jim Matthews. */ /************************************************************************/ -int SHPAPI_CALL DBFIsAttributeNULL(DBFHandle psDBF, int iRecord, int iField) +int SHPAPI_CALL DBFIsAttributeNULL(const DBFHandle psDBF, int iRecord, + int iField) { const char *pszValue = DBFReadStringAttribute(psDBF, iRecord, iField); @@ -1185,7 +1186,7 @@ int SHPAPI_CALL DBFIsAttributeNULL(DBFHandle psDBF, int iRecord, int iField) /* Return the number of fields in this table. */ /************************************************************************/ -int SHPAPI_CALL DBFGetFieldCount(DBFHandle psDBF) +int SHPAPI_CALL DBFGetFieldCount(const DBFHandle psDBF) { return (psDBF->nFields); } @@ -1196,7 +1197,7 @@ int SHPAPI_CALL DBFGetFieldCount(DBFHandle psDBF) /* Return the number of records in this table. */ /************************************************************************/ -int SHPAPI_CALL DBFGetRecordCount(DBFHandle psDBF) +int SHPAPI_CALL DBFGetRecordCount(const DBFHandle psDBF) { return (psDBF->nRecords); } @@ -1209,7 +1210,7 @@ int SHPAPI_CALL DBFGetRecordCount(DBFHandle psDBF) /* bytes long. */ /************************************************************************/ -DBFFieldType SHPAPI_CALL DBFGetFieldInfo(DBFHandle psDBF, int iField, +DBFFieldType SHPAPI_CALL DBFGetFieldInfo(const DBFHandle psDBF, int iField, char *pszFieldName, int *pnWidth, int *pnDecimals) { @@ -1621,7 +1622,8 @@ const char SHPAPI_CALL1(*) DBFReadTuple(DBFHandle psDBF, int hEntity) /* Read one of the attribute fields of a record. */ /************************************************************************/ -DBFHandle SHPAPI_CALL DBFCloneEmpty(DBFHandle psDBF, const char *pszFilename) +DBFHandle SHPAPI_CALL DBFCloneEmpty(const DBFHandle psDBF, + const char *pszFilename) { DBFHandle newDBF = DBFCreateEx(pszFilename, psDBF->pszCodePage); if (newDBF == SHPLIB_NULLPTR) @@ -1680,7 +1682,7 @@ DBFHandle SHPAPI_CALL DBFCloneEmpty(DBFHandle psDBF, const char *pszFilename) /* 'M' (Memo: 10 digits .DBT block ptr) */ /************************************************************************/ -char SHPAPI_CALL DBFGetNativeFieldType(DBFHandle psDBF, int iField) +char SHPAPI_CALL DBFGetNativeFieldType(const DBFHandle psDBF, int iField) { if (iField >= 0 && iField < psDBF->nFields) return psDBF->pachFieldType[iField]; @@ -1696,7 +1698,8 @@ char SHPAPI_CALL DBFGetNativeFieldType(DBFHandle psDBF, int iField) /* Contributed by Jim Matthews. */ /************************************************************************/ -int SHPAPI_CALL DBFGetFieldIndex(DBFHandle psDBF, const char *pszFieldName) +int SHPAPI_CALL DBFGetFieldIndex(const DBFHandle psDBF, + const char *pszFieldName) { char name[XBASE_FLDNAME_LEN_READ + 1]; @@ -1716,7 +1719,7 @@ int SHPAPI_CALL DBFGetFieldIndex(DBFHandle psDBF, const char *pszFieldName) /* it returns FALSE. */ /************************************************************************/ -int SHPAPI_CALL DBFIsRecordDeleted(DBFHandle psDBF, int iShape) +int SHPAPI_CALL DBFIsRecordDeleted(const DBFHandle psDBF, int iShape) { /* -------------------------------------------------------------------- */ /* Verify selection. */ @@ -1779,7 +1782,7 @@ int SHPAPI_CALL DBFMarkRecordDeleted(DBFHandle psDBF, int iShape, /* DBFGetCodePage */ /************************************************************************/ -const char SHPAPI_CALL1(*) DBFGetCodePage(DBFHandle psDBF) +const char SHPAPI_CALL1(*) DBFGetCodePage(const DBFHandle psDBF) { if (psDBF == SHPLIB_NULLPTR) return SHPLIB_NULLPTR; diff --git a/ogr/ogrsf_frmts/shape/sbnsearch.c b/ogr/ogrsf_frmts/shape/sbnsearch.c index 6cb870020b25..be080210d7a8 100644 --- a/ogr/ogrsf_frmts/shape/sbnsearch.c +++ b/ogr/ogrsf_frmts/shape/sbnsearch.c @@ -709,7 +709,7 @@ static int compare_ints(const void *a, const void *b) /* SBNSearchDiskTree() */ /************************************************************************/ -int *SBNSearchDiskTree(SBNSearchHandle hSBN, const double *padfBoundsMin, +int *SBNSearchDiskTree(const SBNSearchHandle hSBN, const double *padfBoundsMin, const double *padfBoundsMax, int *pnShapeCount) { *pnShapeCount = 0; @@ -808,7 +808,7 @@ int *SBNSearchDiskTree(SBNSearchHandle hSBN, const double *padfBoundsMin, /* SBNSearchDiskTreeInteger() */ /************************************************************************/ -int *SBNSearchDiskTreeInteger(SBNSearchHandle hSBN, int bMinX, int bMinY, +int *SBNSearchDiskTreeInteger(const SBNSearchHandle hSBN, int bMinX, int bMinY, int bMaxX, int bMaxY, int *pnShapeCount) { *pnShapeCount = 0; diff --git a/ogr/ogrsf_frmts/shape/shapefil.h b/ogr/ogrsf_frmts/shape/shapefil.h index 83a77b20a5ee..e1832fae1b45 100644 --- a/ogr/ogrsf_frmts/shape/shapefil.h +++ b/ogr/ogrsf_frmts/shape/shapefil.h @@ -22,29 +22,6 @@ #include "cpl_conv.h" #endif -#if !defined(SHP_BIG_ENDIAN) -#if defined(CPL_MSB) -#define SHP_BIG_ENDIAN 1 -#elif (defined(__GNUC__) && __GNUC__ >= 5) || \ - (defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 4 && \ - __GNUC_MINOR__ >= 6) -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -#define SHP_BIG_ENDIAN 1 -#endif -#elif defined(__GLIBC__) -#if __BYTE_ORDER == __BIG_ENDIAN -#define SHP_BIG_ENDIAN 1 -#endif -#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) -#define SHP_BIG_ENDIAN 1 -#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) -#elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || \ - defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || \ - defined(_MIPSEB) || defined(_POWER) || defined(__s390__) -#define SHP_BIG_ENDIAN 1 -#endif -#endif - #ifdef __cplusplus extern "C" { @@ -315,13 +292,13 @@ extern "C" SHPHandle SHPAPI_CALL SHPCreate(const char *pszShapeFile, int nShapeType); SHPHandle SHPAPI_CALL SHPCreateLL(const char *pszShapeFile, int nShapeType, const SAHooks *psHooks); - void SHPAPI_CALL SHPGetInfo(SHPHandle hSHP, int *pnEntities, + void SHPAPI_CALL SHPGetInfo(const SHPHandle hSHP, int *pnEntities, int *pnShapeType, double *padfMinBound, double *padfMaxBound); - SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle hSHP, int iShape); + SHPObject SHPAPI_CALL1(*) SHPReadObject(const SHPHandle hSHP, int iShape); int SHPAPI_CALL SHPWriteObject(SHPHandle hSHP, int iShape, - SHPObject *psObject); + const SHPObject *psObject); void SHPAPI_CALL SHPDestroyObject(SHPObject *psObject); void SHPAPI_CALL SHPComputeExtents(SHPObject *psObject); @@ -334,7 +311,7 @@ extern "C" SHPCreateSimpleObject(int nSHPType, int nVertices, const double *padfX, const double *padfY, const double *padfZ); - int SHPAPI_CALL SHPRewindObject(SHPHandle hSHP, SHPObject *psObject); + int SHPAPI_CALL SHPRewindObject(const SHPHandle hSHP, SHPObject *psObject); void SHPAPI_CALL SHPClose(SHPHandle hSHP); void SHPAPI_CALL SHPWriteHeader(SHPHandle hSHP); @@ -392,7 +369,7 @@ extern "C" void SHPAPI_CALL SHPTreeTrimExtraNodes(SHPTree *hTree); int SHPAPI_CALL1(*) - SHPTreeFindLikelyShapes(SHPTree *hTree, double *padfBoundsMin, + SHPTreeFindLikelyShapes(const SHPTree *hTree, double *padfBoundsMin, double *padfBoundsMax, int *); int SHPAPI_CALL SHPCheckBoundsOverlap(const double *, const double *, const double *, const double *, int); @@ -409,8 +386,9 @@ extern "C" void SHPAPI_CALL SHPCloseDiskTree(SHPTreeDiskHandle hDiskTree); int SHPAPI_CALL1(*) - SHPSearchDiskTreeEx(SHPTreeDiskHandle hDiskTree, double *padfBoundsMin, - double *padfBoundsMax, int *pnShapeCount); + SHPSearchDiskTreeEx(const SHPTreeDiskHandle hDiskTree, + double *padfBoundsMin, double *padfBoundsMax, + int *pnShapeCount); int SHPAPI_CALL SHPWriteTreeLL(SHPTree *hTree, const char *pszFilename, const SAHooks *psHooks); @@ -427,12 +405,14 @@ extern "C" void SHPAPI_CALL SBNCloseDiskTree(SBNSearchHandle hSBN); int SHPAPI_CALL1(*) - SBNSearchDiskTree(SBNSearchHandle hSBN, const double *padfBoundsMin, + SBNSearchDiskTree(const SBNSearchHandle hSBN, + const double *padfBoundsMin, const double *padfBoundsMax, int *pnShapeCount); int SHPAPI_CALL1(*) - SBNSearchDiskTreeInteger(SBNSearchHandle hSBN, int bMinX, int bMinY, - int bMaxX, int bMaxY, int *pnShapeCount); + SBNSearchDiskTreeInteger(const SBNSearchHandle hSBN, int bMinX, + int bMinY, int bMaxX, int bMaxY, + int *pnShapeCount); void SHPAPI_CALL SBNSearchFreeIds(int *panShapeId); @@ -520,8 +500,8 @@ extern "C" const char *pszCodePage, const SAHooks *psHooks); - int SHPAPI_CALL DBFGetFieldCount(DBFHandle psDBF); - int SHPAPI_CALL DBFGetRecordCount(DBFHandle psDBF); + int SHPAPI_CALL DBFGetFieldCount(const DBFHandle psDBF); + int SHPAPI_CALL DBFGetRecordCount(const DBFHandle psDBF); int SHPAPI_CALL DBFAddField(DBFHandle hDBF, const char *pszFieldName, DBFFieldType eType, int nWidth, int nDecimals); @@ -537,11 +517,12 @@ extern "C" const char *pszFieldName, char chType, int nWidth, int nDecimals); - DBFFieldType SHPAPI_CALL DBFGetFieldInfo(DBFHandle psDBF, int iField, + DBFFieldType SHPAPI_CALL DBFGetFieldInfo(const DBFHandle psDBF, int iField, char *pszFieldName, int *pnWidth, int *pnDecimals); - int SHPAPI_CALL DBFGetFieldIndex(DBFHandle psDBF, const char *pszFieldName); + int SHPAPI_CALL DBFGetFieldIndex(const DBFHandle psDBF, + const char *pszFieldName); int SHPAPI_CALL DBFReadIntegerAttribute(DBFHandle hDBF, int iShape, int iField); @@ -553,7 +534,8 @@ extern "C" DBFReadLogicalAttribute(DBFHandle hDBF, int iShape, int iField); SHPDate SHPAPI_CALL DBFReadDateAttribute(DBFHandle hDBF, int iShape, int iField); - int SHPAPI_CALL DBFIsAttributeNULL(DBFHandle hDBF, int iShape, int iField); + int SHPAPI_CALL DBFIsAttributeNULL(const DBFHandle hDBF, int iShape, + int iField); int SHPAPI_CALL DBFWriteIntegerAttribute(DBFHandle hDBF, int iShape, int iField, int nFieldValue); @@ -577,18 +559,18 @@ extern "C" int SHPAPI_CALL DBFWriteTuple(DBFHandle psDBF, int hEntity, const void *pRawTuple); - int SHPAPI_CALL DBFIsRecordDeleted(DBFHandle psDBF, int iShape); + int SHPAPI_CALL DBFIsRecordDeleted(const DBFHandle psDBF, int iShape); int SHPAPI_CALL DBFMarkRecordDeleted(DBFHandle psDBF, int iShape, int bIsDeleted); - DBFHandle SHPAPI_CALL DBFCloneEmpty(DBFHandle psDBF, + DBFHandle SHPAPI_CALL DBFCloneEmpty(const DBFHandle psDBF, const char *pszFilename); void SHPAPI_CALL DBFClose(DBFHandle hDBF); void SHPAPI_CALL DBFUpdateHeader(DBFHandle hDBF); - char SHPAPI_CALL DBFGetNativeFieldType(DBFHandle hDBF, int iField); + char SHPAPI_CALL DBFGetNativeFieldType(const DBFHandle hDBF, int iField); - const char SHPAPI_CALL1(*) DBFGetCodePage(DBFHandle psDBF); + const char SHPAPI_CALL1(*) DBFGetCodePage(const DBFHandle psDBF); void SHPAPI_CALL DBFSetLastModifiedDate(DBFHandle psDBF, int nYYSince1900, int nMM, int nDD); diff --git a/ogr/ogrsf_frmts/shape/shapefil_private.h b/ogr/ogrsf_frmts/shape/shapefil_private.h index f8bfbc14b92a..86d11b512024 100644 --- a/ogr/ogrsf_frmts/shape/shapefil_private.h +++ b/ogr/ogrsf_frmts/shape/shapefil_private.h @@ -28,6 +28,29 @@ #define SHPLIB_NULLPTR NULL #endif +#if !defined(SHP_BIG_ENDIAN) +#if defined(CPL_MSB) +#define SHP_BIG_ENDIAN 1 +#elif (defined(__GNUC__) && __GNUC__ >= 5) || \ + (defined(__GNUC__) && defined(__GNUC_MINOR__) && __GNUC__ == 4 && \ + __GNUC_MINOR__ >= 6) +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SHP_BIG_ENDIAN 1 +#endif +#elif defined(__GLIBC__) +#if __BYTE_ORDER == __BIG_ENDIAN +#define SHP_BIG_ENDIAN 1 +#endif +#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) +#define SHP_BIG_ENDIAN 1 +#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) +#elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || \ + defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || \ + defined(_MIPSEB) || defined(_POWER) || defined(__s390__) +#define SHP_BIG_ENDIAN 1 +#endif +#endif + #include "shapefil.h" #include #include diff --git a/ogr/ogrsf_frmts/shape/shpopen.c b/ogr/ogrsf_frmts/shape/shpopen.c index b609c253ffdb..c4a139d3d689 100644 --- a/ogr/ogrsf_frmts/shape/shpopen.c +++ b/ogr/ogrsf_frmts/shape/shpopen.c @@ -924,8 +924,9 @@ void SHPAPI_CALL SHPSetFastModeReadObject(SHPHandle hSHP, int bFastMode) /* Fetch general information about the shape file. */ /************************************************************************/ -void SHPAPI_CALL SHPGetInfo(SHPHandle psSHP, int *pnEntities, int *pnShapeType, - double *padfMinBound, double *padfMaxBound) +void SHPAPI_CALL SHPGetInfo(const SHPHandle psSHP, int *pnEntities, + int *pnShapeType, double *padfMinBound, + double *padfMaxBound) { if (psSHP == SHPLIB_NULLPTR) return; @@ -1310,7 +1311,7 @@ SHPObject SHPAPI_CALL1(*) /************************************************************************/ int SHPAPI_CALL SHPWriteObject(SHPHandle psSHP, int nShapeId, - SHPObject *psObject) + const SHPObject *psObject) { psSHP->bUpdated = TRUE; @@ -1882,7 +1883,7 @@ static unsigned char *SHPReallocObjectBufIfNecessary(SHPHandle psSHP, /* for one shape. */ /************************************************************************/ -SHPObject SHPAPI_CALL1(*) SHPReadObject(SHPHandle psSHP, int hEntity) +SHPObject SHPAPI_CALL1(*) SHPReadObject(const SHPHandle psSHP, int hEntity) { /* -------------------------------------------------------------------- */ /* Validate the record/entity number. */ @@ -2913,7 +2914,7 @@ static int SHPRewindIsInnerRing(const SHPObject *psObject, int iOpRing, /* specification. */ /************************************************************************/ -int SHPAPI_CALL SHPRewindObject(SHPHandle hSHP, SHPObject *psObject) +int SHPAPI_CALL SHPRewindObject(const SHPHandle hSHP, SHPObject *psObject) { (void)hSHP; /* -------------------------------------------------------------------- */ diff --git a/ogr/ogrsf_frmts/shape/shptree.c b/ogr/ogrsf_frmts/shape/shptree.c index 7321f044ecdd..97331ea2bcc7 100644 --- a/ogr/ogrsf_frmts/shape/shptree.c +++ b/ogr/ogrsf_frmts/shape/shptree.c @@ -501,7 +501,8 @@ int SHPAPI_CALL SHPTreeAddShapeId(SHPTree *psTree, SHPObject *psObject) /* tree node by tree node basis. */ /************************************************************************/ -static void SHPTreeCollectShapeIds(SHPTree *hTree, SHPTreeNode *psTreeNode, +static void SHPTreeCollectShapeIds(const SHPTree *hTree, + const SHPTreeNode *psTreeNode, double *padfBoundsMin, double *padfBoundsMax, int *pnShapeCount, int *pnMaxShapes, int **ppanShapeList) @@ -566,7 +567,7 @@ static int SHPTreeCompareInts(const void *a, const void *b) } int SHPAPI_CALL1(*) - SHPTreeFindLikelyShapes(SHPTree *hTree, double *padfBoundsMin, + SHPTreeFindLikelyShapes(const SHPTree *hTree, double *padfBoundsMin, double *padfBoundsMax, int *pnShapeCount) { @@ -598,7 +599,6 @@ int SHPAPI_CALL1(*) /************************************************************************/ static int SHPTreeNodeTrim(SHPTreeNode *psTreeNode) - { int i; @@ -657,7 +657,6 @@ static int SHPTreeNodeTrim(SHPTreeNode *psTreeNode) /************************************************************************/ void SHPAPI_CALL SHPTreeTrimExtraNodes(SHPTree *hTree) - { SHPTreeNodeTrim(hTree->psRoot); } @@ -713,7 +712,7 @@ void SHPCloseDiskTree(SHPTreeDiskHandle hDiskTree) /* SHPSearchDiskTreeNode() */ /************************************************************************/ -static bool SHPSearchDiskTreeNode(SHPTreeDiskHandle hDiskTree, +static bool SHPSearchDiskTreeNode(const SHPTreeDiskHandle hDiskTree, double *padfBoundsMin, double *padfBoundsMax, int **ppanResultBuffer, int *pnBufferMax, int *pnResultCount, int bNeedSwap, @@ -907,8 +906,9 @@ int SHPAPI_CALL1(*) SHPSearchDiskTree(FILE *fp, double *padfBoundsMin, /************************************************************************/ int SHPAPI_CALL1(*) - SHPSearchDiskTreeEx(SHPTreeDiskHandle hDiskTree, double *padfBoundsMin, - double *padfBoundsMax, int *pnShapeCount) + SHPSearchDiskTreeEx(const SHPTreeDiskHandle hDiskTree, + double *padfBoundsMin, double *padfBoundsMax, + int *pnShapeCount) { int nBufferMax = 0; From 0cd09e3a0b4cad0e34132ae4d0c39714c37b49a1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 27 Feb 2024 04:12:19 +0100 Subject: [PATCH 03/50] [Lint] WMS: use more C++ objects in minidriver_tiled_wms --- frmts/wms/minidriver_tiled_wms.cpp | 317 +++++++++++++---------------- frmts/wms/minidriver_tiled_wms.h | 8 +- 2 files changed, 147 insertions(+), 178 deletions(-) diff --git a/frmts/wms/minidriver_tiled_wms.cpp b/frmts/wms/minidriver_tiled_wms.cpp index 48bdc10139ae..f15674fb8467 100644 --- a/frmts/wms/minidriver_tiled_wms.cpp +++ b/frmts/wms/minidriver_tiled_wms.cpp @@ -62,13 +62,15 @@ #include "wmsdriver.h" #include "minidriver_tiled_wms.h" +#include + static const char SIG[] = "GDAL_WMS TiledWMS: "; /* *\brief Read a number from an xml element */ -static double getXMLNum(CPLXMLNode *poRoot, const char *pszPath, +static double getXMLNum(const CPLXMLNode *poRoot, const char *pszPath, const char *pszDefault) { // Sets errno return CPLAtof(CPLGetXMLValue(poRoot, pszPath, pszDefault)); @@ -79,7 +81,7 @@ static double getXMLNum(CPLXMLNode *poRoot, const char *pszPath, * */ -static GDALColorEntry GetXMLColorEntry(CPLXMLNode *p) +static GDALColorEntry GetXMLColorEntry(const CPLXMLNode *p) { GDALColorEntry ce; ce.c1 = static_cast(getXMLNum(p, "c1", "0")); @@ -109,7 +111,8 @@ static GDALColorEntry GetXMLColorEntry(CPLXMLNode *p) * @return The first matching node or NULL on failure. */ -static CPLXMLNode *SearchXMLSiblings(CPLXMLNode *psRoot, const char *pszElement) +static const CPLXMLNode *SearchXMLSiblings(const CPLXMLNode *psRoot, + const char *pszElement) { if (psRoot == nullptr || pszElement == nullptr) @@ -235,25 +238,24 @@ static int FindBbox(CPLString in) * @param ret The return value, a matching request or an empty string */ -static void FindChangePattern(char *cdata, char **substs, char **keys, - CPLString &ret) +static void FindChangePattern(const char *cdata, const char *const *substs, + const char *const *keys, CPLString &ret) { - char **papszTokens = CSLTokenizeString2( - cdata, " \t\n\r", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES); + const CPLStringList aosTokens(CSLTokenizeString2( + cdata, " \t\n\r", CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES)); ret.clear(); int matchcount = CSLCount(substs); int keycount = CSLCount(keys); if (keycount < matchcount) { - CSLDestroy(papszTokens); return; } // A valid string has only the keys in the substs list and none other - for (int j = 0; j < CSLCount(papszTokens); j++) + for (int j = 0; j < aosTokens.size(); j++) { - ret = papszTokens[j]; // The target string + ret = aosTokens[j]; // The target string bool matches = true; for (int k = 0; k < keycount && keys != nullptr; k++) @@ -288,24 +290,16 @@ static void FindChangePattern(char *cdata, char **substs, char **keys, } // Key loop if (matches) { - CSLDestroy(papszTokens); return; // We got the string ready, all keys accounted for and // substs applied } } ret.clear(); - CSLDestroy(papszTokens); } -WMSMiniDriver_TiledWMS::WMSMiniDriver_TiledWMS() - : m_requests(nullptr), m_bsx(0), m_bsy(0) -{ -} +WMSMiniDriver_TiledWMS::WMSMiniDriver_TiledWMS() = default; -WMSMiniDriver_TiledWMS::~WMSMiniDriver_TiledWMS() -{ - CSLDestroy(m_requests); -} +WMSMiniDriver_TiledWMS::~WMSMiniDriver_TiledWMS() = default; // Returns the scale of a WMS request as compared to the base resolution double WMSMiniDriver_TiledWMS::Scale(const char *request) const @@ -321,7 +315,8 @@ double WMSMiniDriver_TiledWMS::Scale(const char *request) const // Finds, extracts, and returns the highest resolution request string from a // list, starting at item i -CPLString WMSMiniDriver_TiledWMS::GetLowestScale(char **&list, int i) const +CPLString WMSMiniDriver_TiledWMS::GetLowestScale(CPLStringList &list, + int i) const { CPLString req; double scale = -1; @@ -339,7 +334,8 @@ CPLString WMSMiniDriver_TiledWMS::GetLowestScale(char **&list, int i) const if (position > -1) { req = list[position]; - list = CSLRemoveStrings(list, position, 1, nullptr); + list.Assign(CSLRemoveStrings(list.StealList(), position, 1, nullptr), + true); } return req; } @@ -352,13 +348,13 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, CPL_UNUSED char **OpenOptions) { CPLErr ret = CE_None; - CPLXMLNode *tileServiceConfig = nullptr; - CPLXMLNode *TG = nullptr; + CPLXMLTreeCloser tileServiceConfig(nullptr); + const CPLXMLNode *TG = nullptr; - char **requests = nullptr; - char **substs = nullptr; - char **keys = nullptr; - char **changes = nullptr; + CPLStringList requests; + CPLStringList substs; + CPLStringList keys; + CPLStringList changes; try { // Parse info from the WMS Service node @@ -387,13 +383,12 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, const char *value = CPLParseNameValue(changes[i], &key); // Add the ${} around the key if (value != nullptr && key != nullptr) - substs = - CSLSetNameValue(substs, CPLOPrintf("${%s}", key), value); + substs.SetNameValue(CPLOPrintf("${%s}", key), value); CPLFree(key); } // Then process the configuration file itself - CPLXMLNode *nodeChange = CPLSearchXMLNode(config, "Change"); + const CPLXMLNode *nodeChange = CPLSearchXMLNode(config, "Change"); while (nodeChange != nullptr) { CPLString key = CPLGetXMLValue(nodeChange, "key", ""); @@ -401,8 +396,7 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, throw CPLOPrintf( "%s Change element needs a non-empty \"key\" attribute", SIG); - substs = CSLSetNameValue(substs, key, - CPLGetXMLValue(nodeChange, "", "")); + substs.SetNameValue(key, CPLGetXMLValue(nodeChange, "", "")); nodeChange = SearchXMLSiblings(nodeChange, "Change"); } @@ -437,26 +431,28 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, } // decodedGTS contains the GetTileService return now - if (nullptr == (tileServiceConfig = CPLParseXMLString(decodedGTS))) + tileServiceConfig.reset(CPLParseXMLString(decodedGTS)); + if (!tileServiceConfig) throw CPLOPrintf("%s Error parsing the GetTileService response", SIG); if (nullptr == - (TG = CPLSearchXMLNode(tileServiceConfig, "TiledPatterns"))) + (TG = CPLSearchXMLNode(tileServiceConfig.get(), "TiledPatterns"))) throw CPLOPrintf( "%s Can't locate TiledPatterns in server response.", SIG); // Get the global base_url and bounding box, these can be overwritten at // the tileGroup level They are just pointers into existing structures, // cleanup is not required - const char *global_base_url = CPLGetXMLValue( - tileServiceConfig, "TiledPatterns.OnlineResource.xlink:href", ""); - CPLXMLNode *global_latlonbbox = - CPLGetXMLNode(tileServiceConfig, "TiledPatterns.LatLonBoundingBox"); - CPLXMLNode *global_bbox = - CPLGetXMLNode(tileServiceConfig, "TiledPatterns.BoundingBox"); - const char *pszProjection = - CPLGetXMLValue(tileServiceConfig, "TiledPatterns.Projection", ""); + const char *global_base_url = + CPLGetXMLValue(tileServiceConfig.get(), + "TiledPatterns.OnlineResource.xlink:href", ""); + const CPLXMLNode *global_latlonbbox = CPLGetXMLNode( + tileServiceConfig.get(), "TiledPatterns.LatLonBoundingBox"); + const CPLXMLNode *global_bbox = + CPLGetXMLNode(tileServiceConfig.get(), "TiledPatterns.BoundingBox"); + const char *pszProjection = CPLGetXMLValue( + tileServiceConfig.get(), "TiledPatterns.Projection", ""); if (pszProjection[0] != 0) m_oSRS.SetFromUserInput( pszProjection, @@ -476,12 +472,12 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, if (nullptr != CPLGetXMLNode(TG, "Key")) { // Collect all keys defined by this tileset - CPLXMLNode *node = CPLGetXMLNode(TG, "Key"); + const CPLXMLNode *node = CPLGetXMLNode(TG, "Key"); while (nullptr != node) { // the TEXT of the Key node const char *val = CPLGetXMLValue(node, nullptr, nullptr); if (nullptr != val) - keys = CSLAddString(keys, val); + keys.AddString(val); node = SearchXMLSiblings(node, "Key"); } } @@ -523,7 +519,7 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, // Bounding box, local, global, local lat-lon, global lat-lon, in this // order - CPLXMLNode *bbox = CPLGetXMLNode(TG, "BoundingBox"); + const CPLXMLNode *bbox = CPLGetXMLNode(TG, "BoundingBox"); if (nullptr == bbox) bbox = global_bbox; if (nullptr == bbox) @@ -569,11 +565,11 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, // order The palette starts initialized with zeros // - GDALColorTable *poColorTable = nullptr; + bool bHasColorTable = false; if ((band_count == 1) && CPLGetXMLNode(TG, "Palette")) { - CPLXMLNode *node = CPLGetXMLNode(TG, "Palette"); + const CPLXMLNode *node = CPLGetXMLNode(TG, "Palette"); int entries = static_cast(getXMLNum(node, "Size", "255")); GDALPaletteInterp eInterp = GPI_RGB; // RGB and RGBA are the same @@ -588,58 +584,50 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, throw CPLOPrintf("%s Palette definition error", SIG); // Create it and initialize it to nothing - try + int start_idx; + int end_idx; + GDALColorEntry ce_start = {0, 0, 0, 255}; + GDALColorEntry ce_end = {0, 0, 0, 255}; + + auto poColorTable = std::make_unique(eInterp); + poColorTable->CreateColorRamp(0, &ce_start, entries - 1, &ce_end); + // Read the values + const CPLXMLNode *p = CPLGetXMLNode(node, "Entry"); + if (p) { - int start_idx; - int end_idx; - GDALColorEntry ce_start = {0, 0, 0, 255}; - GDALColorEntry ce_end = {0, 0, 0, 255}; - - poColorTable = new GDALColorTable(eInterp); - poColorTable->CreateColorRamp(0, &ce_start, entries - 1, - &ce_end); - // Read the values - CPLXMLNode *p = CPLGetXMLNode(node, "Entry"); - if (p) - { - // Initialize the first entry - start_idx = static_cast(getXMLNum(p, "idx", "0")); - ce_start = GetXMLColorEntry(p); + // Initialize the first entry + start_idx = static_cast(getXMLNum(p, "idx", "0")); + ce_start = GetXMLColorEntry(p); - if (start_idx < 0) - throw CPLOPrintf("%s Palette index %d not allowed", SIG, - start_idx); + if (start_idx < 0) + throw CPLOPrintf("%s Palette index %d not allowed", SIG, + start_idx); - poColorTable->SetColorEntry(start_idx, &ce_start); - while (nullptr != (p = SearchXMLSiblings(p, "Entry"))) - { - // For every entry, create a ramp - ce_end = GetXMLColorEntry(p); - end_idx = static_cast(getXMLNum( - p, "idx", CPLOPrintf("%d", start_idx + 1))); - if ((end_idx <= start_idx) || (start_idx >= entries)) - throw CPLOPrintf("%s Index Error at index %d", SIG, - end_idx); - - poColorTable->CreateColorRamp(start_idx, &ce_start, - end_idx, &ce_end); - ce_start = ce_end; - start_idx = end_idx; - } + poColorTable->SetColorEntry(start_idx, &ce_start); + while (nullptr != (p = SearchXMLSiblings(p, "Entry"))) + { + // For every entry, create a ramp + ce_end = GetXMLColorEntry(p); + end_idx = static_cast( + getXMLNum(p, "idx", CPLOPrintf("%d", start_idx + 1))); + if ((end_idx <= start_idx) || (start_idx >= entries)) + throw CPLOPrintf("%s Index Error at index %d", SIG, + end_idx); + + poColorTable->CreateColorRamp(start_idx, &ce_start, end_idx, + &ce_end); + ce_start = ce_end; + start_idx = end_idx; } - - // Dataset has ownership - m_parent_dataset->SetColorTable(poColorTable); - } - catch (const CPLString &) - { - delete poColorTable; - throw; } + + // Dataset has ownership + m_parent_dataset->SetColorTable(poColorTable.release()); + bHasColorTable = true; } // If palette int overview_count = 0; - CPLXMLNode *Pattern = TG->psChild; + const CPLXMLNode *Pattern = TG->psChild; m_bsx = -1; m_bsy = -1; @@ -659,62 +647,47 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, if (request.empty()) break; // No point to drag, this level doesn't match the keys - char **papszTokens = CSLTokenizeString2(request, "&", 0); - - try - { - const char *pszWIDTH = CSLFetchNameValue(papszTokens, "WIDTH"); - const char *pszHEIGHT = - CSLFetchNameValue(papszTokens, "HEIGHT"); - if (pszWIDTH == nullptr || pszHEIGHT == nullptr) - throw CPLOPrintf( - "%s Cannot find width or height parameters in %s", SIG, - request.c_str()); - - mbsx = atoi(pszWIDTH); - mbsy = atoi(pszHEIGHT); - // If unset until now, try to get the projection from the - // pattern - if (m_oSRS.IsEmpty()) - { - const char *pszSRS = - CSLFetchNameValueDef(papszTokens, "SRS", ""); - if (pszSRS[0] != 0) - m_oSRS = ProjToSRS(pszSRS); - } + const CPLStringList aosTokens(CSLTokenizeString2(request, "&", 0)); - if (-1 == m_bsx) - m_bsx = mbsx; - if (-1 == m_bsy) - m_bsy = mbsy; - if ((m_bsx != mbsx) || (m_bsy != mbsy)) - throw CPLOPrintf("%s Tileset uses different block sizes", - SIG); - - if (CPLsscanf(CSLFetchNameValueDef(papszTokens, "BBOX", ""), - "%lf,%lf,%lf,%lf", &x, &y, &X, &Y) != 4) - throw CPLOPrintf("%s Error parsing BBOX, pattern %d\n", SIG, - overview_count + 1); - - // Pick the largest size - sx = - static_cast((m_data_window.m_x1 - m_data_window.m_x0) / - (X - x) * m_bsx); - sy = static_cast( - fabs((m_data_window.m_y1 - m_data_window.m_y0) / (Y - y) * - m_bsy)); - if (sx > m_data_window.m_sx) - m_data_window.m_sx = sx; - if (sy > m_data_window.m_sy) - m_data_window.m_sy = sy; - } - catch (const CPLString &) + const char *pszWIDTH = aosTokens.FetchNameValue("WIDTH"); + const char *pszHEIGHT = aosTokens.FetchNameValue("HEIGHT"); + if (pszWIDTH == nullptr || pszHEIGHT == nullptr) + throw CPLOPrintf( + "%s Cannot find width or height parameters in %s", SIG, + request.c_str()); + + mbsx = atoi(pszWIDTH); + mbsy = atoi(pszHEIGHT); + // If unset until now, try to get the projection from the + // pattern + if (m_oSRS.IsEmpty()) { - CSLDestroy(papszTokens); - throw; + const char *pszSRS = aosTokens.FetchNameValueDef("SRS", ""); + if (pszSRS[0] != 0) + m_oSRS = ProjToSRS(pszSRS); } - CSLDestroy(papszTokens); + if (-1 == m_bsx) + m_bsx = mbsx; + if (-1 == m_bsy) + m_bsy = mbsy; + if ((m_bsx != mbsx) || (m_bsy != mbsy)) + throw CPLOPrintf("%s Tileset uses different block sizes", SIG); + + if (CPLsscanf(aosTokens.FetchNameValueDef("BBOX", ""), + "%lf,%lf,%lf,%lf", &x, &y, &X, &Y) != 4) + throw CPLOPrintf("%s Error parsing BBOX, pattern %d\n", SIG, + overview_count + 1); + + // Pick the largest size + sx = static_cast((m_data_window.m_x1 - m_data_window.m_x0) / + (X - x) * m_bsx); + sy = static_cast(fabs( + (m_data_window.m_y1 - m_data_window.m_y0) / (Y - y) * m_bsy)); + if (sx > m_data_window.m_sx) + m_data_window.m_sx = sx; + if (sy > m_data_window.m_sy) + m_data_window.m_sy = sy; // Only use overlays where the top coordinate is within a pixel from // the top of coverage @@ -723,7 +696,7 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, m_bsy * modf(fabs((Y - m_data_window.m_y0) / (Y - y)), &temp); if ((pix_off < 1) || ((m_bsy - pix_off) < 1)) { - requests = CSLAddString(requests, request); + requests.AddString(request); overview_count++; } else @@ -737,7 +710,7 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, } // Search for matching TilePattern // Did we find anything - if (CSLCount(requests) == 0) + if (requests.empty()) throw CPLOPrintf("Can't find any usable TilePattern, maybe the " "Changes are not correct?"); @@ -772,7 +745,7 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, if (endBbox == std::string::npos) endBbox = request.size(); request.replace(startBbox, endBbox - startBbox, "${GDAL_BBOX}"); - requests = CSLInsertString(requests, i, request); + requests.InsertString(i, request); // Create the Rasterband or overview for (int j = 1; j <= band_count; j++) @@ -785,7 +758,7 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, { // Base resolution GDALWMSRasterBand *band = new GDALWMSRasterBand(m_parent_dataset, j, 1); - if (poColorTable != nullptr) + if (bHasColorTable) band->SetColorInterpretation(GCI_PaletteIndex); else band->SetColorInterpretation(BandInterp(band_count, j)); @@ -801,18 +774,19 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, if (0 != CSLCount(OpenOptions)) { // Get the proposed XML, it will exist at this point - CPLXMLNode *cfg_root = CPLParseXMLString( - m_parent_dataset->GetMetadataItem("XML", "WMS")); + CPLXMLTreeCloser cfg_root(CPLParseXMLString( + m_parent_dataset->GetMetadataItem("XML", "WMS"))); char *pszXML = nullptr; - if (cfg_root != nullptr) + if (cfg_root) { bool modified = false; // Set openoption StoreConfiguration to Yes to save the server // GTS in the output XML if (CSLFetchBoolean(OpenOptions, "StoreConfiguration", 0) && - nullptr == CPLGetXMLNode(cfg_root, "Service.Configuration")) + nullptr == + CPLGetXMLNode(cfg_root.get(), "Service.Configuration")) { char *xmlencodedGTS = CPLEscapeString( decodedGTS, static_cast(decodedGTS.size()), @@ -821,8 +795,8 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, // It doesn't have a Service.Configuration element, safe to // add one CPLXMLNode *scfg = CPLCreateXMLElementAndValue( - CPLGetXMLNode(cfg_root, "Service"), "Configuration", - xmlencodedGTS); + CPLGetXMLNode(cfg_root.get(), "Service"), + "Configuration", xmlencodedGTS); CPLAddXMLAttributeAndValue(scfg, "encoding", "XMLencoded"); modified = true; CPLFree(xmlencodedGTS); @@ -830,30 +804,32 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, // Set the TiledGroupName if it's not already there and we have // it as an open option - if (!CPLGetXMLNode(cfg_root, "Service.TiledGroupName") && + if (!CPLGetXMLNode(cfg_root.get(), "Service.TiledGroupName") && nullptr != CSLFetchNameValue(OpenOptions, "TiledGroupName")) { CPLCreateXMLElementAndValue( - CPLGetXMLNode(cfg_root, "Service"), "TiledGroupName", + CPLGetXMLNode(cfg_root.get(), "Service"), + "TiledGroupName", CSLFetchNameValue(OpenOptions, "TiledGroupName")); modified = true; } - if (0 != CSLCount(substs)) + if (!substs.empty()) { // Get all the existing Change elements - char **existing_keys = nullptr; - auto nodechange = CPLGetXMLNode(cfg_root, "Service.Change"); + std::set oExistingKeys; + auto nodechange = + CPLGetXMLNode(cfg_root.get(), "Service.Change"); while (nodechange) { const char *key = CPLGetXMLValue(nodechange, "Key", nullptr); if (key) - existing_keys = CSLAddString(existing_keys, key); + oExistingKeys.insert(key); nodechange = nodechange->psNext; } - for (int i = 0, n = CSLCount(substs); i < n && substs; i++) + for (int i = 0, n = substs.size(); i < n && substs; i++) { CPLString kv(substs[i]); auto sep_pos = @@ -864,11 +840,11 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, CPLString val(kv.substr(sep_pos + 1)); // Add to the cfg_root if this change is not already // there - if (-1 == CSLFindString(existing_keys, key)) + if (oExistingKeys.find(key) == oExistingKeys.end()) { auto cnode = CPLCreateXMLElementAndValue( - CPLGetXMLNode(cfg_root, "Service"), "Change", - val); + CPLGetXMLNode(cfg_root.get(), "Service"), + "Change", val); CPLAddXMLAttributeAndValue(cnode, "Key", key); modified = true; } @@ -877,13 +853,12 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, if (modified) { - pszXML = CPLSerializeXMLTree(cfg_root); + pszXML = CPLSerializeXMLTree(cfg_root.get()); m_parent_dataset->SetXML(pszXML); } } CPLFree(pszXML); - CPLDestroyXMLNode(cfg_root); } } catch (const CPLString &msg) @@ -892,13 +867,7 @@ CPLErr WMSMiniDriver_TiledWMS::Initialize(CPLXMLNode *config, CPLError(ret, CPLE_AppDefined, "%s", msg.c_str()); } - CSLDestroy(keys); - CSLDestroy(substs); - CSLDestroy(changes); - if (tileServiceConfig) - CPLDestroyXMLNode(tileServiceConfig); - - m_requests = requests; + m_requests = std::move(requests); return ret; } @@ -909,7 +878,7 @@ CPLErr WMSMiniDriver_TiledWMS::TiledImageRequest( CPLString &url = request.URL; url = m_base_url; URLPrepare(url); - url += CSLGetField(m_requests, -tiri.m_level); + url += CSLGetField(m_requests.List(), -tiri.m_level); URLSearchAndReplace(&url, "${GDAL_BBOX}", "%013.8f,%013.8f,%013.8f,%013.8f", iri.m_x0, iri.m_y1, iri.m_x1, iri.m_y0); return CE_None; diff --git a/frmts/wms/minidriver_tiled_wms.h b/frmts/wms/minidriver_tiled_wms.h index deecbde900da..5e7a30748b0e 100644 --- a/frmts/wms/minidriver_tiled_wms.h +++ b/frmts/wms/minidriver_tiled_wms.h @@ -47,11 +47,11 @@ class WMSMiniDriver_TiledWMS : public WMSMiniDriver protected: double Scale(const char *request) const; - CPLString GetLowestScale(char **&list, int i) const; + CPLString GetLowestScale(CPLStringList &list, int i) const; GDALWMSDataWindow m_data_window; - char **m_requests; - int m_bsx; - int m_bsy; + CPLStringList m_requests{}; + int m_bsx = 0; + int m_bsy = 0; }; #endif /* MINIDRIVER_TILED_WMS_H_INCLUDED */ From 2881a18b05328c5a92b8b07ad09e9834fb34b9e9 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 13:12:57 +0100 Subject: [PATCH 04/50] gdalinfo -json: fix wrong axis order in STAC proj:shape member (fixes #9337) --- apps/gdalinfo_lib.cpp | 40 +++++++++++++++++-------- autotest/utilities/test_gdalinfo_lib.py | 13 ++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/apps/gdalinfo_lib.cpp b/apps/gdalinfo_lib.cpp index 577fc617b962..13524b5b507e 100644 --- a/apps/gdalinfo_lib.cpp +++ b/apps/gdalinfo_lib.cpp @@ -342,19 +342,33 @@ char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions) if (bJson) { - json_object *poSize = json_object_new_array(); - json_object *poSizeX = - json_object_new_int(GDALGetRasterXSize(hDataset)); - json_object *poSizeY = - json_object_new_int(GDALGetRasterYSize(hDataset)); - - json_object_array_add(poSize, poSizeX); - json_object_array_add(poSize, poSizeY); - - json_object *poStacSize = nullptr; - json_object_deep_copy(poSize, &poStacSize, nullptr); - json_object_object_add(poJsonObject, "size", poSize); - json_object_object_add(poStac, "proj:shape", poStacSize); + { + json_object *poSize = json_object_new_array(); + json_object *poSizeX = + json_object_new_int(GDALGetRasterXSize(hDataset)); + json_object *poSizeY = + json_object_new_int(GDALGetRasterYSize(hDataset)); + + // size is X, Y ordered + json_object_array_add(poSize, poSizeX); + json_object_array_add(poSize, poSizeY); + + json_object_object_add(poJsonObject, "size", poSize); + } + + { + json_object *poStacSize = json_object_new_array(); + json_object *poSizeX = + json_object_new_int(GDALGetRasterXSize(hDataset)); + json_object *poSizeY = + json_object_new_int(GDALGetRasterYSize(hDataset)); + + // ... but ... proj:shape is Y, X ordered. + json_object_array_add(poStacSize, poSizeY); + json_object_array_add(poStacSize, poSizeX); + + json_object_object_add(poStac, "proj:shape", poStacSize); + } } else { diff --git a/autotest/utilities/test_gdalinfo_lib.py b/autotest/utilities/test_gdalinfo_lib.py index a170b880affc..af751909b8cd 100755 --- a/autotest/utilities/test_gdalinfo_lib.py +++ b/autotest/utilities/test_gdalinfo_lib.py @@ -263,3 +263,16 @@ def test_gdalinfo_lib_json_projjson_no_epsg(): ret = gdal.Info(ds, options="-json") assert ret["stac"]["proj:epsg"] is None assert ret["stac"]["proj:wkt2"] is not None + + +############################################################################### +# Test fix for https://github.com/OSGeo/gdal/issues/9337 + + +def test_gdalinfo_lib_json_proj_shape(): + + width = 2 + height = 1 + ds = gdal.GetDriverByName("MEM").Create("", width, height) + ret = gdal.Info(ds, options="-json") + assert ret["stac"]["proj:shape"] == [height, width] From 27893e787258647505b7ea48088fd9a1f41e6615 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Wed, 28 Feb 2024 14:52:01 +0100 Subject: [PATCH 05/50] gdalinfo_output.schema.json: add comment about size and proj:shape ordering --- apps/data/gdalinfo_output.schema.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/data/gdalinfo_output.schema.json b/apps/data/gdalinfo_output.schema.json index 04055fcdabe9..166a490d9e26 100644 --- a/apps/data/gdalinfo_output.schema.json +++ b/apps/data/gdalinfo_output.schema.json @@ -176,6 +176,7 @@ } }, "size": { + "$comment": "note that the order of items in side is width,height", "$ref": "#/definitions/arrayOfTwoIntegers" }, "coordinateSystem": { @@ -306,6 +307,7 @@ }, "proj:shape": { + "$comment": "note that the order of items in proj:shape is height,width starting with GDAL 3.8.5 (previous versions ordered it wrongly as width,height)", "title": "Shape", "type": "array", "minItems": 2, From 6a332607d8bc0f828af3a2a30a2306c4ca2412ca Mon Sep 17 00:00:00 2001 From: Andrea Giudiceandrea Date: Wed, 28 Feb 2024 18:16:28 +0100 Subject: [PATCH 06/50] gdal_polygonize.py: handle error if creation of dst ds fails --- swig/python/gdal-utils/osgeo_utils/gdal_polygonize.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/swig/python/gdal-utils/osgeo_utils/gdal_polygonize.py b/swig/python/gdal-utils/osgeo_utils/gdal_polygonize.py index 73b0d70cbd47..9fbd361168b7 100644 --- a/swig/python/gdal-utils/osgeo_utils/gdal_polygonize.py +++ b/swig/python/gdal-utils/osgeo_utils/gdal_polygonize.py @@ -146,6 +146,9 @@ def gdal_polygonize( if not quiet: print("Creating output %s of format %s." % (dst_filename, driver_name)) dst_ds = drv.CreateDataSource(dst_filename) + if dst_ds is None: + print('Cannot create datasource "%s"' % dst_filename) + return 1 # ============================================================================= # Find or create destination layer. From 2f8f685d3e9d57a52ffb9fdaa0abab428f1f6cef Mon Sep 17 00:00:00 2001 From: Daniel Baston Date: Tue, 27 Feb 2024 22:11:14 -0500 Subject: [PATCH 07/50] Doc: Add Python docstrings for some functions in gdal module --- swig/include/python/docs/gdal_docs.i | 365 +++++++++++++++++++++++++++ swig/include/python/gdal_python.i | 1 + swig/python/CMakeLists.txt | 1 + 3 files changed, 367 insertions(+) create mode 100644 swig/include/python/docs/gdal_docs.i diff --git a/swig/include/python/docs/gdal_docs.i b/swig/include/python/docs/gdal_docs.i new file mode 100644 index 000000000000..2d783a8d88aa --- /dev/null +++ b/swig/include/python/docs/gdal_docs.i @@ -0,0 +1,365 @@ +// gdal.AllRegister +%feature("docstring") GDALAllRegister " + +Register all known configured GDAL drivers. +Automatically called when the :py:mod:`gdal` module is loaded. +Does not need to be called in user code unless a driver was +deregistered and needs to be re-registered. +See :cpp:func:`GDALAllRegister`. + +See Also +-------- +:py:func:`Driver.Register` +"; + +// gdal.GetCacheMax +%feature("docstring") wrapper_GDALGetCacheMax " + +Get the maximum size of the block cache. +See :cpp:func:`GDALGetCacheMax`. + +Returns +------- +int + maximum cache size in bytes +"; + +// gdal.GetCacheUsed +%feature("docstring") wrapper_GDALGetCacheUsed " + +Get the number of bytes in used by the block cache. +See :cpp:func:`GDALGetCacheUsed`. + +Returns +------- +int + cache size in bytes +"; + +// gdal.GetConfigOption +%feature("docstring") wrapper_CPLGetConfigOption " + +Return the value of a configuration option. +See :cpp:func:`CPLGetConfigOption`. + +Parameters +---------- +pszKey : str + name of the configuration option +pszDefault : str, optional + default value to return if the option has not been set + +Returns +------- +str + +See Also +-------- +:py:func:`GetConfigOptions` +:py:func:`GetThreadLocalConfigOption` +"; + +// gdal.GetConfigOptions +%feature("docstring") wrapper_GetConfigOptions " + +Return a dictionary of currently set configuration options. +See :cpp:func:`CPLGetConfigOptions`. + +Returns +------- +dict + +Examples +-------- +>>> with gdal.config_options({'A': '3', 'B': '4'}): +... gdal.SetConfigOption('C', '5') +... gdal.GetConfigOptions() +... +{'C': '5', 'A': '3', 'B': '4'} + +See Also +-------- +:py:func:`GetConfigOption` +:py:func:`GetGlobalConfigOptions` +"; + +// gdal.GetDataTypeByName +%feature("docstring") GDALGetDataTypeByName " + +Return the data type for a given name. + +Parameters +---------- +pszDataTypeName : str + data type name + +Returns +------- +int + data type code + +Examples +-------- +>>> gdal.GetDataTypeByName('Int16') == gdal.GDT_Int16 +True + +" + +// gdal.GetDataTypeName +%feature("docstring") GDALGetDataTypeName " + +Return the name of the data type. + +Parameters +---------- +eDataType : int + data type code + +Returns +------- +str + +Examples +-------- +>>> gdal.GetDataTypeName(gdal.GDT_Int16) +'Int16' +>>> gdal.GetDataTypeName(gdal.GDT_Float64) +'Float64' +"; + +// gdal.GetDataTypeSize +%feature("docstring") GDALGetDataTypeSize " + +Return the size of the data type in bits. + +Parameters +---------- +eDataType : int + data type code + +Returns +------- +int + +Examples +-------- +>>> gdal.GetDataTypeSize(gdal.GDT_Byte) +8 +>>> gdal.GetDataTypeSize(gdal.GDT_Int32) +32 +"; + +// gdal.GetDriverCount +%feature("docstring") GetDriverCount " + +Return the number of registered drivers. +See :cpp:func:`GDALGetDriverCount`. + +Examples +-------- +>>> gdal.GetDriverCount() +227 +>>> gdal.GetDriverByName('ESRI Shapefile').Deregister() +>>> gdal.GetDriverCount() +226 + +"; + +// gdal.GetGlobalConfigOption +%feature("docstring") wrapper_CPLGetGlobalConfigOption " + +Return the value of a global (not thread-local) configuration option. +See :cpp:func:`CPLGetGlobalConfigOption`. + +Parameters +---------- +pszKey : str + name of the configuration option +pszDefault : str, optional + default value to return if the option has not been set + +Returns +------- +str +"; + +// gdal.GetGlobalConfigOptions +%feature("docstring") wrapper_GetGlobalConfigOptions " + +Return a dictionary of currently set configuration options, +excluding thread-local configuration options. + +Returns +------- +dict + +See Also +-------- +:py:func:`GetConfigOption` +:py:func:`GetConfigOptions` +"; + +// gdal.GetNumCPUs +%feature("docstring") CPLGetNumCPUs " + +Return the number of processors detected by GDAL. + +Returns +------- +int +"; + +// gdal.GetThreadLocalConfigOption +%feature("docstring") wrapper_CPLGetThreadLocalConfigOption " + +Return the value of a thread-local configuration option. +See :cpp:func:`CPLGetThreadLocalConfigOption`. + +Parameters +---------- +pszKey : str + name of the configuration option +pszDefault : str, optional + default value to return if the option has not been set + +Returns +------- +str + +"; + +// gdal.Open +%feature("docstring") Open " + +Opens a raster file as a :py:class:`Dataset` using default options. +See :cpp:func:`GDALOpen`. +For more control over how the file is opened, use :py:func:`OpenEx`. + +Parameters +---------- +utf8_path : str + name of the file to open +eAccess : int, default = :py:const:`gdal.GA_ReadOnly` + +Returns +------- +Dataset, or ``None`` on failure + +See Also +-------- +:py:func:`OpenEx` +:py:func:`OpenShared` + +"; + +// gdal.OpenEx +%feature("docstring") OpenEx " + +Open a raster or vector file as a :py:class:`Dataset`. +See :cpp:func:`GDALOpenEx`. + +Parameters +---------- +utf8_path : str + name of the file to open +flags : int + Flags controlling how the Dataset is opened. Multiple ``gdal.OF_XXX`` flags + may be combined using the ``|`` operator. See :cpp:func:`GDALOpenEx`. +allowed_drivers : list, optional + A list of the names of drivers that may attempt to open the dataset. +open_options : dict/list, optional + A dict or list of name=value driver-specific opening options. +sibling_files: list, optional + A list of filenames that are auxiliary to the main filename + +Returns +------- +Dataset, or ``None`` on failure. + +See Also +-------- +:py:func:`Open` +:py:func:`OpenShared` + +"; + +// gdal.OpenShared +%feature("docstring") OpenShared " + +Open a raster file as a :py:class:`Dataset`. If the file has already been +opened in the current thread, return a reference to the already-opened +:py:class:`Dataset`. See :cpp:func:`GDALOpenShared`. + +Parameters +---------- +utf8_path : str + name of the file to open +eAccess : int, default = :py:const:`gdal.GA_ReadOnly` + +Returns +------- +Dataset, or ``None`` on failure + +See Also +-------- +:py:func:`Open` +:py:func:`OpenEx` + +"; + +// gdal.SetCacheMax +%feature("docstring") wrapper_GDALSetCacheMax " + +Set the maximum size of the block cache. +See :cpp:func:`GDALSetCacheMax`. + +Parameters +---------- +nBytes: int + Cache size in bytes + +See Also +-------- +:config:`GDAL_CACHEMAX` +"; + +// gdal.SetConfigOption +%feature("docstring") CPLSetConfigOption " + +Set the value of a configuration option for all threads. +See :cpp:func:`CPLSetConfigOption`. + +Parameters +---------- +pszKey : str + name of the configuration option +pszValue : str + value of the configuration option + +See Also +-------- +:py:func:`SetThreadLocalConfigOption` +:py:func:`config_option` +:py:func:`config_options` + +"; + +// gdal.SetThreadLocalConfigOption +%feature("docstring") CPLSetThreadLocalConfigOption " + +Set the value of a configuration option for the current thread. +See :cpp:func:`CPLSetThreadLocalConfigOption`. + +Parameters +---------- +pszKey : str + name of the configuration option +pszValue : str + value of the configuration option + +See Also +-------- +:py:func:`SetConfigOption` +:py:func:`config_option` +:py:func:`config_options` +"; diff --git a/swig/include/python/gdal_python.i b/swig/include/python/gdal_python.i index 947d77e04c01..968ab566da26 100644 --- a/swig/include/python/gdal_python.i +++ b/swig/include/python/gdal_python.i @@ -7,6 +7,7 @@ %feature("autodoc"); +%include "gdal_docs.i" %include "gdal_band_docs.i" %include "gdal_dataset_docs.i" diff --git a/swig/python/CMakeLists.txt b/swig/python/CMakeLists.txt index 8db7378db034..4983f4901439 100644 --- a/swig/python/CMakeLists.txt +++ b/swig/python/CMakeLists.txt @@ -31,6 +31,7 @@ set(GDAL_PYTHON_CSOURCES ${PROJECT_SOURCE_DIR}/swig/include/python/python_exceptions.i ${PROJECT_SOURCE_DIR}/swig/include/python/python_strings.i ${PROJECT_SOURCE_DIR}/swig/include/python/typemaps_python.i + ${PROJECT_SOURCE_DIR}/swig/include/python/docs/gdal_docs.i ${PROJECT_SOURCE_DIR}/swig/include/python/docs/gdal_band_docs.i ${PROJECT_SOURCE_DIR}/swig/include/python/docs/gdal_dataset_docs.i ${PROJECT_SOURCE_DIR}/swig/include/python/docs/ogr_datasource_docs.i From 2e03708d99c6b5196e7b7755de0edd21c3fb8c54 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 12:26:01 +0100 Subject: [PATCH 08/50] VRT/gdal_translate -of 200% 200%: make sure that the synthetized virtual overviews match the dimension of the source ones when possible --- autotest/gcore/vrt_read.py | 40 ++++++++++++++++++++++++++++++++++++++ frmts/vrt/vrtdataset.cpp | 37 +++++++++++++++++++++++++++++++---- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/autotest/gcore/vrt_read.py b/autotest/gcore/vrt_read.py index f6abca4c25c0..0bc59ed1c072 100755 --- a/autotest/gcore/vrt_read.py +++ b/autotest/gcore/vrt_read.py @@ -2586,3 +2586,43 @@ def test_vrt_read_compute_statistics_approximate(tmp_vsimem): assert "STATISTICS_MEAN" in md assert "STATISTICS_STDDEV" in md assert md["STATISTICS_VALID_PERCENT"] == "100" + + +############################################################################### +# Test that when generating virtual overviews we select them as close as +# possible to source ones + + +def test_vrt_read_virtual_overviews_match_src_overviews(tmp_vsimem): + + gtiff_filename = str(tmp_vsimem / "tmp.tif") + ds = gdal.GetDriverByName("GTiff").Create( + gtiff_filename, 4095, 2047, options=["SPARSE_OK=YES"] + ) + ds.BuildOverviews("NONE", [2, 4, 8]) + band = ds.GetRasterBand(1) + # The below assert is a pre-condition to test the behavior of the + # "vrt://{gtiff_filename}?outsize=200%,200%". If we decided to round + # differently overview dataset sizes in the GTiff driver, we'd need to + # change this source dataset + assert band.GetOverview(0).XSize == 2048 + assert band.GetOverview(0).YSize == 1024 + assert band.GetOverview(1).XSize == 1024 + assert band.GetOverview(1).YSize == 512 + ds = None + + vrt_ds = gdal.Open(f"vrt://{gtiff_filename}?outsize=200%,200%") + assert vrt_ds.RasterXSize == 4095 * 2 + assert vrt_ds.RasterYSize == 2047 * 2 + vrt_band = vrt_ds.GetRasterBand(1) + assert vrt_band.XSize == 4095 * 2 + assert vrt_band.YSize == 2047 * 2 + assert vrt_band.GetOverviewCount() == 3 + # We reuse the dimension of the source dataset + assert vrt_band.GetOverview(0).XSize == 4095 + assert vrt_band.GetOverview(0).YSize == 2047 + # We reuse the dimension of the overviews of the source dataset + assert vrt_band.GetOverview(1).XSize == 2048 + assert vrt_band.GetOverview(1).YSize == 1024 + assert vrt_band.GetOverview(2).XSize == 1024 + assert vrt_band.GetOverview(2).YSize == 512 diff --git a/frmts/vrt/vrtdataset.cpp b/frmts/vrt/vrtdataset.cpp index 5e99d7437ed6..9331480d9bea 100644 --- a/frmts/vrt/vrtdataset.cpp +++ b/frmts/vrt/vrtdataset.cpp @@ -36,6 +36,8 @@ #include "gdal_utils.h" #include +#include +#include #include #include "gdal_proxy.h" @@ -2436,6 +2438,7 @@ void VRTDataset::UnsetPreservedRelativeFilenames() static bool CheckBandForOverview(GDALRasterBand *poBand, GDALRasterBand *&poFirstBand, int &nOverviews, + std::set> &oSetOvrSizes, std::vector &apoOverviewsBak) { if (!cpl::down_cast(poBand)->IsSourcedRasterBand()) @@ -2462,6 +2465,17 @@ static bool CheckBandForOverview(GDALRasterBand *poBand, // To prevent recursion apoOverviewsBak.push_back(nullptr); const int nOvrCount = poSrcBand->GetOverviewCount(); + oSetOvrSizes.insert( + std::pair(poSrcBand->GetXSize(), poSrcBand->GetYSize())); + for (int i = 0; i < nOvrCount; ++i) + { + auto poSrcOvrBand = poSrcBand->GetOverview(i); + if (poSrcOvrBand) + { + oSetOvrSizes.insert(std::pair(poSrcOvrBand->GetXSize(), + poSrcOvrBand->GetYSize())); + } + } apoOverviewsBak.resize(0); if (nOvrCount == 0) @@ -2488,18 +2502,19 @@ void VRTDataset::BuildVirtualOverviews() int nOverviews = 0; GDALRasterBand *poFirstBand = nullptr; + std::set> oSetOvrSizes; for (int iBand = 0; iBand < nBands; iBand++) { if (!CheckBandForOverview(papoBands[iBand], poFirstBand, nOverviews, - m_apoOverviewsBak)) + oSetOvrSizes, m_apoOverviewsBak)) return; } if (m_poMaskBand) { if (!CheckBandForOverview(m_poMaskBand, poFirstBand, nOverviews, - m_apoOverviewsBak)) + oSetOvrSizes, m_apoOverviewsBak)) return; } if (poFirstBand == nullptr) @@ -2531,10 +2546,24 @@ void VRTDataset::BuildVirtualOverviews() { continue; } - const int nOvrXSize = static_cast(0.5 + nRasterXSize * dfXRatio); - const int nOvrYSize = static_cast(0.5 + nRasterYSize * dfYRatio); + int nOvrXSize = static_cast(0.5 + nRasterXSize * dfXRatio); + int nOvrYSize = static_cast(0.5 + nRasterYSize * dfYRatio); if (nOvrXSize < 128 || nOvrYSize < 128) break; + + // Look for a source overview whose size is very close to the + // theoretical computed one. + for (const auto &ovrSize : oSetOvrSizes) + { + if (std::abs(ovrSize.first - nOvrXSize) <= 1 && + std::abs(ovrSize.second - nOvrYSize) <= 1) + { + nOvrXSize = ovrSize.first; + nOvrYSize = ovrSize.second; + break; + } + } + VRTDataset *poOvrVDS = new VRTDataset(nOvrXSize, nOvrYSize); m_apoOverviews.push_back(poOvrVDS); From 6f0381818a9fe062d4dd21f6476904fa53bdde5e Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 12:50:06 +0100 Subject: [PATCH 09/50] ILI2: emit an error and not just a warning when creating a dataset without a model file Refs https://github.com/OSGeo/gdal/pull/9351 / https://github.com/qgis/QGIS/issues/56588 --- autotest/ogr/ogr_ili.py | 20 ++++++++++++++++++++ ogr/ogrsf_frmts/ili/ogrili2datasource.cpp | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/autotest/ogr/ogr_ili.py b/autotest/ogr/ogr_ili.py index 67155ba7ab56..b545d4756dbb 100755 --- a/autotest/ogr/ogr_ili.py +++ b/autotest/ogr/ogr_ili.py @@ -34,6 +34,7 @@ pytestmark = [ pytest.mark.require_driver("Interlis 1"), + pytest.mark.require_driver("Interlis 2"), pytest.mark.random_order(disabled=True), ] @@ -1197,3 +1198,22 @@ def test_ogr_interlis_arc2(): for i in range(feat.GetGeomFieldCount()): geom = feat.GetGeomFieldRef(i) ogrtest.check_feature_geometry(geom, geom_field_values[i]) + + +############################################################################### +# Test failure in creation of ILI2 dataset + + +def test_ogr_interlis2_create_file_error(): + + with pytest.raises( + Exception, match="model file not specified in destination filename" + ): + ogr.GetDriverByName("Interlis 2").CreateDataSource("tmp/out.xtf") + + with pytest.raises( + Exception, match="Failed to create XTF file /i_do/not/exist/out.xtf" + ): + ogr.GetDriverByName("Interlis 2").CreateDataSource( + "/i_do/not/exist/out.xtf,data/ili/ch.bazl.sicherheitszonenplan.oereb_20131118.imd" + ) diff --git a/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp b/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp index 12531c01a21f..ff6476c558d5 100644 --- a/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp +++ b/ogr/ogrsf_frmts/ili/ogrili2datasource.cpp @@ -192,7 +192,9 @@ int OGRILI2DataSource::Create(const char *pszFilename, if (pszModelFilename == nullptr) { - CPLError(CE_Warning, CPLE_AppDefined, "Model file not specified."); + CPLError( + CE_Failure, CPLE_AppDefined, + "ILI2 Create(): model file not specified in destination filename."); CSLDestroy(filenames); return FALSE; } From 941fafc4f2ff25a7dc9f6872760854a6ae4463fe Mon Sep 17 00:00:00 2001 From: Alessandro Pasotti Date: Thu, 29 Feb 2024 14:05:10 +0100 Subject: [PATCH 10/50] OGR: fix read corrupted wkb with mixed 2D and 3D geoms Fixes #9326 --- ogr/ogrgeometrycollection.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ogr/ogrgeometrycollection.cpp b/ogr/ogrgeometrycollection.cpp index 4476bd396cd6..d074ff898718 100644 --- a/ogr/ogrgeometrycollection.cpp +++ b/ogr/ogrgeometrycollection.cpp @@ -557,6 +557,22 @@ OGRErr OGRGeometryCollection::importFromWkbInternal( eErr = OGRGeometryFactory::createFromWkb( pabySubData, nullptr, &poSubGeom, nSize, eWkbVariant, nSubGeomBytesConsumed); + + if (eErr == OGRERR_NONE) + { + // if this is a Z or M geom make sure the sub geoms are as well + if (Is3D() && !poSubGeom->Is3D()) + { + CPLDebug("OGR", "Promoting sub-geometry to 3D"); + poSubGeom->set3D(TRUE); + } + + if (IsMeasured() && !poSubGeom->IsMeasured()) + { + CPLDebug("OGR", "Promoting sub-geometry to Measured"); + poSubGeom->setMeasured(TRUE); + } + } } if (eErr != OGRERR_NONE) From 950fc54d03e85bf9928f61ed9cba08154663b0ad Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 29 Feb 2024 17:31:56 +0100 Subject: [PATCH 11/50] VRTPansharpenedDataset: allow to specify for and --- autotest/gdrivers/vrtpansharpen.py | 69 ++++++++++++++++++++++++++++++ doc/source/drivers/raster/vrt.rst | 13 ++++++ frmts/vrt/data/gdalvrt.xsd | 2 + frmts/vrt/vrtpansharpened.cpp | 40 +++++++++++------ 4 files changed, 110 insertions(+), 14 deletions(-) diff --git a/autotest/gdrivers/vrtpansharpen.py b/autotest/gdrivers/vrtpansharpen.py index 58d158734d22..5215a6a25c10 100755 --- a/autotest/gdrivers/vrtpansharpen.py +++ b/autotest/gdrivers/vrtpansharpen.py @@ -2304,3 +2304,72 @@ def test_vrtpansharpen_out_of_order_input_bands_and_nodata(): cs2 = [vrt_ds.GetRasterBand(i + 1).Checksum() for i in range(vrt_ds.RasterCount)] assert cs2 == cs[::-1] + + +############################################################################### +# Test open options for input bands + + +def test_vrtpansharpen_open_options_input_bands(): + def my_handler(typ, errno, msg): + msgs.append(msg) + + msgs = [] + with gdaltest.error_handler(my_handler): + gdal.Open( + """ + + + tmp/small_world_pan.tif + + foo + + 1 + + + data/small_world.tif + 1 + + + data/small_world.tif + 2 + + + data/small_world.tif + 3 + + + """ + ) + # Not the prettiest way to check that open options are used, but that does the job... + assert "small_world_pan.tif: Invalid value for NUM_THREADS: foo" in msgs + + msgs = [] + with gdaltest.error_handler(my_handler): + gdal.Open( + """ + + + tmp/small_world_pan.tif + 1 + + + data/small_world.tif + + foo + + 1 + + + data/small_world.tif + 2 + + + data/small_world.tif + 3 + + + """ + ) + # Not the prettiest way to check that open options are used, but that does the job... + assert "small_world.tif: Invalid value for NUM_THREADS: foo" in msgs diff --git a/doc/source/drivers/raster/vrt.rst b/doc/source/drivers/raster/vrt.rst index 49476c354b1f..1c860c439ef2 100644 --- a/doc/source/drivers/raster/vrt.rst +++ b/doc/source/drivers/raster/vrt.rst @@ -1757,6 +1757,7 @@ PanchroBand and SpectralBand elements must have at least a **SourceFilename** ch element to specify the name of the dataset. They may also have a **SourceBand** child element to specify the number of the band in the dataset (starting with 1). If not specify, the first band will be assumed. +**OpenOptions** can also be specified The SpectralBand element must generally have a **dstBand** attribute to specify the number of the output band (starting with 1) to which the input spectral band must be mapped. @@ -1778,18 +1779,30 @@ with panchromatic.tif. panchromatic.tif + + ALL_CPUS + 1 multispectral.tif + + ALL_CPUS + 1 multispectral.tif + + ALL_CPUS + 2 multispectral.tif + + ALL_CPUS + 3 diff --git a/frmts/vrt/data/gdalvrt.xsd b/frmts/vrt/data/gdalvrt.xsd index cc9c13cba596..5e204df9ab98 100644 --- a/frmts/vrt/data/gdalvrt.xsd +++ b/frmts/vrt/data/gdalvrt.xsd @@ -138,6 +138,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/frmts/vrt/vrtpansharpened.cpp b/frmts/vrt/vrtpansharpened.cpp index e055848eb2fd..d9fb0e076167 100644 --- a/frmts/vrt/vrtpansharpened.cpp +++ b/frmts/vrt/vrtpansharpened.cpp @@ -351,8 +351,12 @@ CPLErr VRTPansharpenedDataset::XMLInit(const CPLXMLNode *psTree, pszSourceFilename = pszAbs; } osSourceFilename = pszSourceFilename; - poPanDataset = - GDALDataset::FromHandle(GDALOpen(osSourceFilename, GA_ReadOnly)); + + const CPLStringList aosOpenOptions( + GDALDeserializeOpenOptionsFromXML(psPanchroBand)); + + poPanDataset = GDALDataset::Open(osSourceFilename, GDAL_OF_RASTER, + nullptr, aosOpenOptions.List()); if (poPanDataset == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, "%s not a valid dataset", @@ -552,8 +556,11 @@ CPLErr VRTPansharpenedDataset::XMLInit(const CPLXMLNode *psTree, poDataset = oMapNamesToDataset[osSourceFilename]; if (poDataset == nullptr) { - poDataset = GDALDataset::FromHandle( - GDALOpen(osSourceFilename, GA_ReadOnly)); + const CPLStringList aosOpenOptions( + GDALDeserializeOpenOptionsFromXML(psIter)); + + poDataset = GDALDataset::Open(osSourceFilename, GDAL_OF_RASTER, + nullptr, aosOpenOptions.List()); if (poDataset == nullptr) { CPLError(CE_Failure, CPLE_AppDefined, @@ -1337,14 +1344,13 @@ CPLXMLNode *VRTPansharpenedDataset::SerializeToXML(const char *pszVRTPathIn) GDALRasterBand::FromHandle(psOptions->hPanchroBand); if (poBand->GetDataset()) { + auto poPanchoDS = poBand->GetDataset(); std::map::iterator oIter = - m_oMapToRelativeFilenames.find( - poBand->GetDataset()->GetDescription()); + m_oMapToRelativeFilenames.find(poPanchoDS->GetDescription()); if (oIter == m_oMapToRelativeFilenames.end()) { - CPLCreateXMLElementAndValue( - psBand, "SourceFilename", - poBand->GetDataset()->GetDescription()); + CPLCreateXMLElementAndValue(psBand, "SourceFilename", + poPanchoDS->GetDescription()); } else { @@ -1355,6 +1361,9 @@ CPLXMLNode *VRTPansharpenedDataset::SerializeToXML(const char *pszVRTPathIn) "relativeToVRT"), CXT_Text, "1"); } + + GDALSerializeOpenOptionsToXML(psBand, poPanchoDS->GetOpenOptions()); + CPLCreateXMLElementAndValue(psBand, "SourceBand", CPLSPrintf("%d", poBand->GetBand())); } @@ -1393,14 +1402,13 @@ CPLXMLNode *VRTPansharpenedDataset::SerializeToXML(const char *pszVRTPathIn) GDALRasterBand::FromHandle(psOptions->pahInputSpectralBands[i]); if (poBand->GetDataset()) { + auto poSpectralDS = poBand->GetDataset(); std::map::iterator oIter = - m_oMapToRelativeFilenames.find( - poBand->GetDataset()->GetDescription()); + m_oMapToRelativeFilenames.find(poSpectralDS->GetDescription()); if (oIter == m_oMapToRelativeFilenames.end()) { - CPLCreateXMLElementAndValue( - psBand, "SourceFilename", - poBand->GetDataset()->GetDescription()); + CPLCreateXMLElementAndValue(psBand, "SourceFilename", + poSpectralDS->GetDescription()); } else { @@ -1411,6 +1419,10 @@ CPLXMLNode *VRTPansharpenedDataset::SerializeToXML(const char *pszVRTPathIn) "relativeToVRT"), CXT_Text, "1"); } + + GDALSerializeOpenOptionsToXML(psBand, + poSpectralDS->GetOpenOptions()); + CPLCreateXMLElementAndValue(psBand, "SourceBand", CPLSPrintf("%d", poBand->GetBand())); } From 1841a93b000f483d073662d3daa5bcb23e7443f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alan=20Ram=C3=ADrez=20Herrera?= <13205973+Alan5142@users.noreply.github.com> Date: Thu, 29 Feb 2024 13:39:52 -0600 Subject: [PATCH 12/50] GeoRaster: Added GENSTATS options, security fixes, and prevent failing when password is near expiration (#9290) --- autotest/gdrivers/georaster.py | 33 +++- doc/source/drivers/raster/georaster.rst | 139 ++++++++++++++--- frmts/georaster/georaster_dataset.cpp | 114 +++++++++++++- frmts/georaster/georaster_priv.h | 13 ++ frmts/georaster/georaster_wrapper.cpp | 199 +++++++++++++++++++++++- frmts/georaster/georasterdrivercore.cpp | 27 ++++ frmts/georaster/oci_wrapper.cpp | 23 ++- 7 files changed, 525 insertions(+), 23 deletions(-) diff --git a/autotest/gdrivers/georaster.py b/autotest/gdrivers/georaster.py index 661fffe4beab..d5e312164dcb 100755 --- a/autotest/gdrivers/georaster.py +++ b/autotest/gdrivers/georaster.py @@ -54,7 +54,7 @@ def get_connection_str(): if oci_dsname is None: # TODO: Spelling - informe? return "" - return "geor:" + oci_dsname.split(":")[1] + return "geor:" + oci_dsname[oci_dsname.index(":") + 1 :] ############################################################################### @@ -488,6 +488,37 @@ def test_georaster_4bit(): # +def test_georaster_genstats(): + if gdaltest.oci_ds is None: + pytest.skip() + + ds_src = gdal.Open("data/byte.tif") + + ds = gdaltest.georasterDriver.CreateCopy( + get_connection_str() + ",GDAL_TEST,RASTER", + ds_src, + 1, + [ + "DESCRIPTION=(id number, raster sdo_georaster)", + "INSERT=(1014, sdo_geor.init('GDAL_TEST_RDT',1014))", + "NBITS=4", + "GENSTATS=TRUE", + ], + ) + + ds_name = ds.GetDescription() + + ds = None + + tst = gdaltest.GDALTest("GeoRaster", ds_name, 1, 2578, filename_absolute=1) + + tst.testOpen() + + +############################################################################### +# + + def test_georaster_cleanup(): if gdaltest.oci_ds is None: pytest.skip() diff --git a/doc/source/drivers/raster/georaster.rst b/doc/source/drivers/raster/georaster.rst index 397e4a19ec6c..e9a31dee6b54 100644 --- a/doc/source/drivers/raster/georaster.rst +++ b/doc/source/drivers/raster/georaster.rst @@ -185,6 +185,102 @@ Creation Options generated. If :co:`GENPYRAMID` is not informed the resample method NN (nearest neighbor) will apply. +- .. co:: GENSTATS + :choices: TRUE, FALSE + :default: FALSE + + To generate statistics from the given rasters, set this value to TRUE. + Default value is FALSE. + This option must be present in order to generate the stats, otherwise, + other GENSTATS options are ignored. + + +- .. co:: GENSTATS_SAMPLINGFACTOR + :choices: + :default: 1 + + Defines the number of cells skipped in both row and column dimensions when + the statistics are computed. + For example, when setting this value to 4, one-sixteenth of the cells are sampled. + The higher the value, the less accurate the statistics are likely to be, + but the more quickly they will be computed. + Defaults to 1, which means that all cells are sampled. + +- .. co:: GENSTATS_SAMPLINGWINDOW + :choices: + + This parameter identifies the upper-left (row, column) and lower-right + (row, column) coordinates of a rectangular window, and raster space is assumed. + The intersection of the MBR (minimum bounding rectangle) of the + GeoRaster object in raster space is used for computing statistics. + When this value is not specified, statistics are computed for the entire raster. + +- .. co:: GENSTATS_HISTOGRAM + :choices: TRUE, FALSE + :default: FALSE + + When this value is set to TRUE, a histogram will be computed and stored. + Defaults to FALSE, so a histogram won't be generated. + +- .. co:: GENSTATS_LAYERNUMBERS + :choices: , - + + Defines the numbers of the layers for which to compute the statistics. + This can include numbers, ranges (indicated by hyphens), and commas + to separate any combination of those. + For example, '1,3-5,7', '1,3,6', '1-6'. + If this value is not specified, statistics will be computed for all + layers. + +- .. co:: GENSTATS_USEBIN + :choices: TRUE, FALSE + :default: TRUE + + Defaults to TRUE. + Specifies whether or not to use a provided bin function + (specified in :co:`GENSTATS_BINFUNCTION`). + When this value is set to TRUE, the bin function to be + used follows the following sequence: (1) the bin function + specified in :co:`GENSTATS_BINFUNCTION`. (2) the bin + function specified by the element in the + GeoRaster XML metadata. (3) a dynamically generated bin + function generated as follows: + Min and max are the actual min and max values of the raster + Numbins is defined as: + * celldepth = 1, numbins = 2. + * cellDepth = 2, numbins = 4. + * cellDepth = 4, numbins = 8. + * cellDepth >= 8, numbins = 256. + + When this value is set to FALSE, then the bin function + to use is the one generated dynamically as previously + described. + +- .. co:: GENSTATS_BINFUNCTION + :choices: + + An array whose element specify the bin type, total + number of bins, first bin number, minimum cell value, + and maximum cell value. Bin type must be linear (0). + When this value is not specified, and :co:`GENSTATS_USEBIN` + is TRUE, then the bin function to use is as follows: + + 1. A valid function defined in the GeoRaster metadata. + 2. The same bin function generated when :co:`GENSTATS_USEBIN` is FALSE. + +- .. co:: GENSTATS_NODATA + :choices: TRUE, FALSE + :default: FALSE + + Specifies whether or not to compare each cell values + with NODATA values defined in the metadata when computing + statistics. When set to TRUE, all pixels with a NODATA + value won't be considered. When set to FALSE, NODATA + pixels will be considered as regular pixels. + + A NODATA value is used for cells whose values are either not known + or meaningless + - .. co:: QUALITY :choices: 0-100 :default: 75 @@ -366,23 +462,28 @@ Loading: General use of GeoRaster ------------------------ -| GeoRaster can be used in any GDAL command line tool with all the - available options. Like a image subset extraction or re-project: -| % gdal_translate -of gtiff geor:scott/tiger@dbdemo,landsat,scene,id=54 - output.tif \\ +GeoRaster can be used in any GDAL command line tool with all the available options. +Like a image subset extraction or re-project: + +.. code-block:: bash + + % gdal_translate -of gtiff geor:scott/tiger@dbdemo,landsat,scene,id=54 output.tif \   -srcwin 0 0 800 600 - % gdalwarp -of png geor:scott/tiger@dbdemo,st_rdt_1,130 output.png - -t_srs EPSG:9000913 - Two different GeoRaster can be used as input and output on the same - operation: -| % gdal_translate -of georaster - geor:scott/tiger@dbdemo,landsat,scene,id=54 - geor:scott/tiger@proj1,projview,image -co INSERT="VALUES - (102, SDO_GEOR.INIT())" - Applications that use GDAL can theoretically read and write from - GeoRaster just like any other format but most of then are more - inclined to try to access files on the file system so one alternative - is to create VRT to represent the GeoRaster description, e.g.: -| % gdal_translate -of VRT geor:scott/tiger@dbdemo,landsat,scene,id=54 - view_54.vrt - % openenv view_54.vrt + % gdalwarp -of png geor:scott/tiger@dbdemo,st_rdt_1,130 output.png + -t_srs EPSG:9000913 + +Two different GeoRaster can be used as input and output on the same operation: + +.. code-block:: bash + + % gdal_translate -of georaster geor:scott/tiger@dbdemo,landsat,scene,id=54 \ + geor:scott/tiger@proj1,projview,image -co INSERT="VALUES (102, SDO_GEOR.INIT())" + +Applications that use GDAL can theoretically read and write from GeoRaster just like +any other format but most of then are more inclined to try to access files on the file +system so one alternative is to create VRT to represent the GeoRaster description, e.g.: + +.. code-block:: bash + + % gdal_translate -of VRT geor:scott/tiger@dbdemo,landsat,scene,id=54 view_54.vrt + % openenv view_54.vrt diff --git a/frmts/georaster/georaster_dataset.cpp b/frmts/georaster/georaster_dataset.cpp index 453a1a9566d4..2328d645e7e6 100644 --- a/frmts/georaster/georaster_dataset.cpp +++ b/frmts/georaster/georaster_dataset.cpp @@ -789,6 +789,21 @@ char **GeoRasterDataset::GetFileList() return papszFileList; } +static bool ParseCommaSeparatedString(const char *str, double pfValues[], + int nMaxValues) +{ + int nValues = 0; + char **papszTokens = CSLTokenizeString2(str, ",", 0); + + for (int i = 0; papszTokens && papszTokens[i] && nValues < nMaxValues; i++) + { + pfValues[nValues++] = CPLAtof(papszTokens[i]); + } + + CSLDestroy(papszTokens); + return nValues == nMaxValues; +} + // --------------------------------------------------------------------------- // Create() // --------------------------------------------------------------------------- @@ -1252,6 +1267,103 @@ GDALDataset *GeoRasterDataset::Create(const char *pszFilename, int nXSize, poGRD->poGeoRaster->nPyramidLevels = atoi(pszFetched); } + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS"); + + if (pszFetched != nullptr) + { + poGRD->poGeoRaster->bGenStats = EQUAL(pszFetched, "TRUE"); + } + + bool bGenStatsOptionsUsed = false; + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_SAMPLINGFACTOR"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->nGenStatsSamplingFactor = atoi(pszFetched); + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_SAMPLINGWINDOW"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + + // Sampling window contains 4 double values + const int nSize = 4; + if (!ParseCommaSeparatedString( + pszFetched, poGRD->poGeoRaster->dfGenStatsSamplingWindow, + nSize)) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Wrong comma separated string for sampling window (%s)", + pszFetched); + delete poGRD; + return nullptr; + } + + poGRD->poGeoRaster->bGenStatsUseSamplingWindow = true; + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_HISTOGRAM"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->bGenStatsHistogram = EQUAL(pszFetched, "TRUE"); + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_LAYERNUMBERS"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->sGenStatsLayerNumbers = pszFetched; + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_USEBIN"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->bGenStatsUseBin = EQUAL(pszFetched, "TRUE"); + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_BINFUNCTION"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + const int nSize = 5; + if (!ParseCommaSeparatedString( + pszFetched, poGRD->poGeoRaster->dfGenStatsBinFunction, nSize)) + { + CPLError(CE_Failure, CPLE_IllegalArg, + "Wrong comma separated string for bin function (%s)", + pszFetched); + delete poGRD; + return nullptr; + } + } + + pszFetched = CSLFetchNameValue(papszOptions, "GENSTATS_NODATA"); + + if (pszFetched != nullptr) + { + bGenStatsOptionsUsed = true; + poGRD->poGeoRaster->bGenStatsNodata = EQUAL(pszFetched, "TRUE"); + } + + if (bGenStatsOptionsUsed && !poGRD->poGeoRaster->bGenStats) + { + CPLError( + CE_Warning, CPLE_AppDefined, + "Some GENSTATS* options were used but GENSTATS is not set. " + "Statistics won't be computed, please set GENSTATS option to true " + "if you want to generate statistics"); + } + // ------------------------------------------------------------------- // Return a new Dataset // ------------------------------------------------------------------- @@ -2421,7 +2533,7 @@ void GeoRasterDataset::SetSubdatasets(GeoRasterWrapper *poGRW) papszSubdatasets = CSLSetNameValue( papszSubdatasets, CPLSPrintf("SUBDATASET_%d_DESC", nCount), - CPLSPrintf("%s.Table=%s", szOwner, szTable)); + CPLSPrintf("Table=%s.%s", szOwner, szTable)); nCount++; } while (poStmt->Fetch()); diff --git a/frmts/georaster/georaster_priv.h b/frmts/georaster/georaster_priv.h index 4812d10994f1..c1eba12b16ab 100644 --- a/frmts/georaster/georaster_priv.h +++ b/frmts/georaster/georaster_priv.h @@ -391,6 +391,9 @@ class GeoRasterWrapper char *GetVAT(int nBand); bool GeneratePyramid(int nLevels, const char *pszResampling, bool bInternal = false); + bool GenerateStatistics(int nSamplingFactor, double *pdfSamplingWindow, + bool bHistogram, const char *pszLayerNumbers, + bool bUseBin, double *pdfBinFunction, bool bNodata); void DeletePyramid(); void PrepareToOverwrite(); bool InitializeMask(int nLevel, int nBlockColumns, int nBlockRows, @@ -465,6 +468,16 @@ class GeoRasterWrapper bool bHasBitmapMask; bool bUniqueFound; + bool bGenStats; + int nGenStatsSamplingFactor; + bool bGenStatsUseSamplingWindow; + double dfGenStatsSamplingWindow[4]; + bool bGenStatsHistogram; + CPLString sGenStatsLayerNumbers; + bool bGenStatsUseBin; + double dfGenStatsBinFunction[5]; + bool bGenStatsNodata; + int eModelCoordLocation; unsigned int anULTCoordinate[3]; diff --git a/frmts/georaster/georaster_wrapper.cpp b/frmts/georaster/georaster_wrapper.cpp index a0d4a7b1a17c..cdb9dbd8c9cb 100644 --- a/frmts/georaster/georaster_wrapper.cpp +++ b/frmts/georaster/georaster_wrapper.cpp @@ -43,7 +43,8 @@ // --------------------------------------------------------------------------- GeoRasterWrapper::GeoRasterWrapper() - : sPyramidResampling("NN"), sCompressionType("NONE"), sInterleaving("BSQ") + : sPyramidResampling("NN"), sCompressionType("NONE"), sInterleaving("BSQ"), + bGenStatsUseSamplingWindow(false), sGenStatsLayerNumbers("") { nRasterId = -1; phMetadata = nullptr; @@ -115,6 +116,20 @@ GeoRasterWrapper::GeoRasterWrapper() pasGCPList = nullptr; nGCPCount = 0; bFlushGCP = false; + bGenStats = false; + nGenStatsSamplingFactor = 1; + bGenStatsHistogram = false; + bGenStatsUseBin = true; + bGenStatsNodata = false; + dfGenStatsBinFunction[0] = 0.0; + dfGenStatsBinFunction[1] = 0.0; + dfGenStatsBinFunction[2] = 0.0; + dfGenStatsBinFunction[3] = 0.0; + dfGenStatsBinFunction[4] = 0.0; + dfGenStatsSamplingWindow[0] = 0.0; + dfGenStatsSamplingWindow[1] = 0.0; + dfGenStatsSamplingWindow[2] = 0.0; + dfGenStatsSamplingWindow[3] = 0.0; } // --------------------------------------------------------------------------- @@ -543,6 +558,75 @@ GeoRasterWrapper *GeoRasterWrapper::Open(const char *pszStringId, bool bUpdate) return poGRW; } +static bool ValidateInsertExpression(const CPLString &sInsertStatement) +{ + bool bInQuotes = false; + const size_t nStrLength = sInsertStatement.length(); + + for (size_t nPos = 0; nPos < nStrLength; ++nPos) + { + const char cCurrentChar = sInsertStatement[nPos]; + + if (cCurrentChar == '\'') + { + if (bInQuotes) + { + if (nPos + 1 < nStrLength && sInsertStatement[nPos + 1] == '\'') + { + ++nPos; + } + else + { + bInQuotes = false; + } + } + else + { + bInQuotes = true; + } + } + else if (!bInQuotes) + { + if (cCurrentChar == ';') + { + return false; + } + else if (nPos < nStrLength - 1) + { + const char cNextChar = sInsertStatement[nPos + 1]; + const bool bIsInvalid = + (cCurrentChar == '-' && cNextChar == '-') || + (cCurrentChar == '/' && cNextChar == '/') || + (cCurrentChar == '/' && cNextChar == '*') || + (cCurrentChar == '*' && cNextChar == '/'); + + if (bIsInvalid) + { + return false; + } + } + } + } + // Quotes must be properly closed, if not, this variable will be true + // thus having an unclosed string + return !bInQuotes; +} + +static bool ValidateDescriptionExpression(const CPLString &sInsertStatement) +{ + const char *rgpszInvalidChars[] = {";", "--", "/*", "*/", "//"}; + + for (const char *pszInvalidChar : rgpszInvalidChars) + { + if (sInsertStatement.find(pszInvalidChar) != std::string::npos) + { + return false; + } + } + + return true; +} + // --------------------------------------------------------------------------- // Create() // --------------------------------------------------------------------------- @@ -601,6 +685,12 @@ bool GeoRasterWrapper::Create(char *pszDescription, char *pszInsert, if (pszDescription) { + if (!ValidateDescriptionExpression(pszDescription)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "DESCRIPTION expression contains invalid values."); + return false; + } snprintf(szDescription, sizeof(szDescription), "%s", pszDescription); } @@ -617,6 +707,14 @@ bool GeoRasterWrapper::Create(char *pszDescription, char *pszInsert, if (pszInsert) { sValues = pszInsert; + sValues.Trim(); // Remove spaces on the left and on the right + + if (!ValidateInsertExpression(sValues)) + { + CPLError(CE_Failure, CPLE_AppDefined, + "INSERT expression contains invalid values."); + return false; + } if (pszInsert[0] == '(' && sValues.ifind("VALUES") == std::string::npos) @@ -3618,6 +3716,22 @@ bool GeoRasterWrapper::FlushMetadata() } } + if (bGenStats) + { + if (GenerateStatistics(nGenStatsSamplingFactor, + dfGenStatsSamplingWindow, bGenStatsHistogram, + sGenStatsLayerNumbers.c_str(), bGenStatsUseBin, + dfGenStatsBinFunction, bGenStatsNodata)) + { + CPLDebug("GEOR", "Generated statistics successfully."); + } + else + { + CPLError(CE_Warning, CPLE_AppDefined, + "Error generating statistics!"); + } + } + return true; } @@ -3775,6 +3889,89 @@ void GeoRasterWrapper::DeletePyramid() delete poStmt; } +// --------------------------------------------------------------------------- +// GenerateStatistics() +// --------------------------------------------------------------------------- +bool GeoRasterWrapper::GenerateStatistics(int nSamplingFactor, + double *pdfSamplingWindow, + bool bHistogram, + const char *pszLayerNumbers, + bool bUseBin, double *pdfBinFunction, + bool bNodata) +{ + // Length is 6 because it's the length of string FALSE. + char szHistogram[] = "FALSE"; + char szNodata[] = "FALSE"; + char szUseBin[] = "FALSE"; + char szUseWin[] = "FALSE"; + if (bHistogram) + { + strncpy(szHistogram, "TRUE", 5); + } + if (bNodata) + { + strncpy(szNodata, "TRUE", 5); + } + if (bUseBin) + { + strncpy(szUseBin, "TRUE", 5); + } + if (bGenStatsUseSamplingWindow) + { + strncpy(szUseWin, "TRUE", 5); + } + + char szLayerNumbers[OWTEXT]; + snprintf(szLayerNumbers, sizeof(szLayerNumbers), "%s", pszLayerNumbers); + + OWStatement *poStmt = poConnection->CreateStatement( + CPLSPrintf("DECLARE\n" + " gr sdo_georaster;\n" + " swin SDO_NUMBER_ARRAY := SDO_NUMBER_ARRAY(:1, :2, :3," + " :4);\n" + " binfunc SDO_NUMBER_ARRAY := SDO_NUMBER_ARRAY(:5, :6, :7," + " :8, :9);\n" + " res VARCHAR2(5);\n" + "BEGIN\n" + " IF :usewin = 'FALSE' THEN\n" + " swin := NULL;\n" + " END IF;" + " IF :usebin = 'FALSE' THEN\n" + " binfunc := NULL;\n" + " END IF;" + " SELECT t.%s INTO gr FROM %s t WHERE %s FOR UPDATE;\n" + " res := sdo_geor.generateStatistics(gr, " + "'samplingFactor='||:samplingfactor, swin,\n" + " :histogram, :layernums, :usebin, binfunc, :nodata);\n" + " UPDATE %s t SET t.%s = gr WHERE %s;\n" + " COMMIT;\n" + "END;\n", + sColumn.c_str(), sTable.c_str(), sWhere.c_str(), + sTable.c_str(), sColumn.c_str(), sWhere.c_str())); + + // Bind sampling window + for (int i = 0; i < 4; i++) + { + poStmt->Bind(&pdfSamplingWindow[i]); + } + // Bind bin function + for (int i = 0; i < 5; i++) + { + poStmt->Bind(&pdfBinFunction[i]); + } + poStmt->BindName(":samplingfactor", &nSamplingFactor); + poStmt->BindName(":layernums", szLayerNumbers); + poStmt->BindName(":histogram", szHistogram); + poStmt->BindName(":nodata", szNodata); + poStmt->BindName(":usebin", szUseBin); + poStmt->BindName(":usewin", szUseWin); + + bool bResult = poStmt->Execute(); + delete poStmt; + + return bResult; +} + // --------------------------------------------------------------------------- // CreateBitmapMask() // --------------------------------------------------------------------------- diff --git a/frmts/georaster/georasterdrivercore.cpp b/frmts/georaster/georasterdrivercore.cpp index 2acafdc91a8f..206358081f6e 100644 --- a/frmts/georaster/georasterdrivercore.cpp +++ b/frmts/georaster/georasterdrivercore.cpp @@ -100,6 +100,33 @@ void GEORDriverSetCommonMetadata(GDALDriver *poDriver) " " "