LCOV - code coverage report
Current view: top level - frmts/postgisraster - postgisrasterdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 210 1527 13.8 %
Date: 2025-01-18 12:42:00 Functions: 11 43 25.6 %

          Line data    Source code
       1             : /***********************************************************************
       2             :  * File :    postgisrasterdataset.cpp
       3             :  * Project:  PostGIS Raster driver
       4             :  * Purpose:  GDAL Dataset implementation for PostGIS Raster driver
       5             :  * Author:   Jorge Arevalo, jorge.arevalo@deimos-space.com
       6             :  *                          jorgearevalo@libregis.org
       7             :  *
       8             :  * Author:       David Zwarg, dzwarg@azavea.com
       9             :  *
      10             :  *
      11             :  ***********************************************************************
      12             :  * Copyright (c) 2009 - 2013, Jorge Arevalo, David Zwarg
      13             :  * Copyright (c) 2013-2018, Even Rouault <even.rouault at spatialys.com>
      14             :  *
      15             :  * SPDX-License-Identifier: MIT
      16             :  **********************************************************************/
      17             : 
      18             : #include "gdal_frmts.h"
      19             : #include "postgisraster.h"
      20             : #include "postgisrasterdrivercore.h"
      21             : #include <math.h>
      22             : 
      23             : #include <algorithm>
      24             : #include <memory>
      25             : 
      26             : #ifdef _WIN32
      27             : #define rint(x) floor((x) + 0.5)
      28             : #endif
      29             : 
      30             : /* PostgreSQL defaults */
      31             : #define DEFAULT_SCHEMA "public"
      32             : #define DEFAULT_COLUMN "rast"
      33             : 
      34             : /** Note on read performance on mode=2:
      35             : 
      36             :     There are different situations:
      37             : 
      38             :      1) the table is registered in the raster_columns table and number of bands,
      39             :    minx,miny,maxx,maxy are available a) no where clause, the table has a primary
      40             :    key and a GIST index on the raster column. If the raster_columns advertise a
      41             :    scale_x and scale_y, use it. Otherwise take the metadata of 10 rasters and
      42             :    compute and average scale_x, scale_y With above information, we can build the
      43             :    dataset definition.
      44             : 
      45             :             (Following logic is implemented in LoadSources())
      46             : 
      47             :             During a IRasterIO() query,
      48             :             i) we will do a SQL query to retrieve the PKID of tiles that
      49             :    intersect the query window. ii) If some tiles are not registered as sources,
      50             :    then do a SQL query to fetch their metadata and instantiate them and register
      51             :    them. iii) If some tiles are not cached, then determine if the query window
      52             :    is not too big (w.r.t. GDAL cache), and if not, then do a SQL query to fetch
      53             :    their raster column.
      54             : 
      55             :             Note: if raster_columns show that all tiles have same dimensions, we
      56             :    can merge the query ii) and iii) in the same one.
      57             : 
      58             :         b) otherwise, do a full scan of metadata to build the sources
      59             : 
      60             :      2) otherwise, throw a warning to the user and do a full scan of metadata is
      61             :    needed to build the sources
      62             : 
      63             :      For 1b) and 2), during a IRasterIO() query, determine which sources are
      64             :    needed and not cached.
      65             : 
      66             :         (Following logic is implemented in IRasterIO())
      67             : 
      68             :         If the query window is not too big,
      69             :             If there's a primary key, then do a SQL query on the IDs of uncached
      70             :    sources to fetch their raster column and cache them. Otherwise if there's a
      71             :    GIST index, do a SQL spatial query on the window to fetch their raster
      72             :    column, and cache them (identification with registered sources is done with
      73             :    the top-left coordinates) Otherwise do a SQL query based on the range of
      74             :    top-left coordinates of tiles that intersect the query window.
      75             : 
      76             :      Conclusion: best performance is achieved with: no where clause, a primary
      77             :    key, a GIST index, known table extent and, with moderate performance
      78             :    advantage :
      79             :                   - same scale_x, scale_y to save an initial SQL query,
      80             :                   - same blocksize_x, blocksize_y to save one SQL query per
      81             :    IRasterIO()
      82             : */
      83             : 
      84             : /************************
      85             :  * \brief Constructor
      86             :  ************************/
      87           2 : PostGISRasterDataset::PostGISRasterDataset()
      88             :     : VRTDataset(0, 0), papszSubdatasets(nullptr), nSrid(-1),
      89             :       nOverviewFactor(1), nBandsToCreate(0), poConn(nullptr),
      90             :       bRegularBlocking(false), bAllTilesSnapToSameGrid(false),
      91             :       bCheckAllTiles(
      92           2 :           CPLTestBool(CPLGetConfigOption("PR_ALLOW_WHOLE_TABLE_SCAN", "YES"))),
      93             :       pszSchema(nullptr), pszTable(nullptr), pszColumn(nullptr),
      94             :       pszWhere(nullptr), pszPrimaryKeyName(nullptr), bIsFastPK(false),
      95             :       bHasTriedFetchingPrimaryKeyName(false),
      96             :       // Default
      97             :       resolutionStrategy(AVERAGE_APPROX_RESOLUTION), nMode(NO_MODE),
      98             :       m_nTiles(0), xmin(0.0), ymin(0.0), xmax(0.0), ymax(0.0),
      99             :       papoSourcesHolders(nullptr), hQuadTree(nullptr),
     100             :       bHasBuiltOverviews(false), nOverviewCount(0), poParentDS(nullptr),
     101             :       papoOverviewDS(nullptr), bAssumeMultiBandReadPattern(true),
     102             :       nNextExpectedBand(1), nXOffPrev(0), nYOffPrev(0), nXSizePrev(0),
     103             :       nYSizePrev(0), bHasTriedHasSpatialIndex(false), bHasSpatialIndex(false),
     104             :       bBuildQuadTreeDynamically(false), bTilesSameDimension(false),
     105           4 :       nTileWidth(0), nTileHeight(0)
     106             : {
     107           2 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     108             : 
     109           2 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = 0.0;
     110           2 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
     111           2 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = 0.0;
     112           2 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
     113             :     // coverity[tainted_data]
     114           2 :     adfGeoTransform[GEOTRSFRM_WE_RES] =
     115           2 :         CPLAtof(CPLGetConfigOption("PR_WE_RES", NO_VALID_RES));
     116             :     // coverity[tainted_data]
     117           2 :     adfGeoTransform[GEOTRSFRM_NS_RES] =
     118           2 :         CPLAtof(CPLGetConfigOption("PR_NS_RES", NO_VALID_RES));
     119             : 
     120           2 :     const char *pszTmp = nullptr;
     121             :     // We ignore this option if we provided the desired resolution
     122           2 :     if (CPLIsEqual(adfGeoTransform[GEOTRSFRM_WE_RES], CPLAtof(NO_VALID_RES)) ||
     123           0 :         CPLIsEqual(adfGeoTransform[GEOTRSFRM_NS_RES], CPLAtof(NO_VALID_RES)))
     124             :     {
     125             : 
     126             :         // Resolution didn't have a valid value, so, we initiate it
     127           2 :         adfGeoTransform[GEOTRSFRM_WE_RES] = 0.0;
     128           2 :         adfGeoTransform[GEOTRSFRM_NS_RES] = 0.0;
     129             : 
     130           2 :         pszTmp = CPLGetConfigOption("PR_RESOLUTION_STRATEGY", "AVERAGE_APPROX");
     131             : 
     132           2 :         if (EQUAL(pszTmp, "LOWEST"))
     133           0 :             resolutionStrategy = LOWEST_RESOLUTION;
     134             : 
     135           2 :         else if (EQUAL(pszTmp, "HIGHEST"))
     136           0 :             resolutionStrategy = HIGHEST_RESOLUTION;
     137             : 
     138           2 :         else if (EQUAL(pszTmp, "USER"))
     139           0 :             resolutionStrategy = USER_RESOLUTION;
     140             : 
     141           2 :         else if (EQUAL(pszTmp, "AVERAGE"))
     142           0 :             resolutionStrategy = AVERAGE_RESOLUTION;
     143             :     }
     144             :     else
     145             :     {
     146           0 :         resolutionStrategy = USER_RESOLUTION;
     147             : #ifdef DEBUG_VERBOSE
     148             :         pszTmp = "USER";
     149             : #endif
     150             :     }
     151             : 
     152             : #ifdef DEBUG_VERBOSE
     153             :     CPLDebug("PostGIS_Raster",
     154             :              "PostGISRasterDataset::Constructor:"
     155             :              "STRATEGY = %s",
     156             :              pszTmp);
     157             : #endif
     158             : 
     159           2 :     poDriver = nullptr;
     160             : 
     161           2 :     nRasterXSize = 0;
     162           2 :     nRasterYSize = 0;
     163             : 
     164           2 :     SetWritable(false);
     165             : 
     166             :     // TODO: Parametrize bAllTilesSnapToSameGrid. It controls if all the
     167             :     // raster rows, in ONE_RASTER_PER_TABLE mode, must be checked to
     168             :     // test if they snap to the same grid and have the same SRID. It can
     169             :     // be the user decision, if he/she's sure all the rows pass the
     170             :     // test and want more speed.
     171           2 : }
     172             : 
     173             : /************************
     174             :  * \brief Constructor
     175             :  ************************/
     176           4 : PostGISRasterDataset::~PostGISRasterDataset()
     177             : {
     178             : 
     179           2 :     if (pszSchema)
     180             :     {
     181           2 :         CPLFree(pszSchema);
     182           2 :         pszSchema = nullptr;
     183             :     }
     184             : 
     185           2 :     if (pszTable)
     186             :     {
     187           2 :         CPLFree(pszTable);
     188           2 :         pszTable = nullptr;
     189             :     }
     190             : 
     191           2 :     if (pszColumn)
     192             :     {
     193           2 :         CPLFree(pszColumn);
     194           2 :         pszColumn = nullptr;
     195             :     }
     196             : 
     197           2 :     if (pszWhere)
     198             :     {
     199           0 :         CPLFree(pszWhere);
     200           0 :         pszWhere = nullptr;
     201             :     }
     202             : 
     203           2 :     if (pszPrimaryKeyName)
     204             :     {
     205           0 :         CPLFree(pszPrimaryKeyName);
     206           0 :         pszPrimaryKeyName = nullptr;
     207             :     }
     208             : 
     209           2 :     if (papszSubdatasets)
     210             :     {
     211           0 :         CSLDestroy(papszSubdatasets);
     212           0 :         papszSubdatasets = nullptr;
     213             :     }
     214             : 
     215           2 :     if (hQuadTree)
     216             :     {
     217           0 :         CPLQuadTreeDestroy(hQuadTree);
     218           0 :         hQuadTree = nullptr;
     219             :     }
     220             : 
     221             :     // Call it now so that the VRT sources
     222             :     // are deleted and that there is no longer any code
     223             :     // referencing the bands of the source holders.
     224             :     // Otherwise this would go wrong because
     225             :     // of the deleting the source holders just below.
     226           2 :     PostGISRasterDataset::CloseDependentDatasets();
     227             : 
     228           2 :     if (papoSourcesHolders)
     229             :     {
     230             :         int i;
     231           0 :         for (i = 0; i < m_nTiles; i++)
     232             :         {
     233           0 :             if (papoSourcesHolders[i])
     234           0 :                 delete papoSourcesHolders[i];
     235             :         }
     236             : 
     237           0 :         VSIFree(papoSourcesHolders);
     238           0 :         papoSourcesHolders = nullptr;
     239             :     }
     240           4 : }
     241             : 
     242             : /************************************************************************/
     243             : /*                        CloseDependentDatasets()                      */
     244             : /************************************************************************/
     245             : 
     246           2 : int PostGISRasterDataset::CloseDependentDatasets()
     247             : {
     248           2 :     int bHasDroppedRef = VRTDataset::CloseDependentDatasets();
     249           2 :     if (nOverviewCount > 0)
     250             :     {
     251             :         int i;
     252           0 :         for (i = 0; i < nOverviewCount; i++)
     253             :         {
     254           0 :             delete papoOverviewDS[i];
     255             :         }
     256           0 :         CPLFree(papoOverviewDS);
     257           0 :         papoOverviewDS = nullptr;
     258           0 :         nOverviewCount = 0;
     259           0 :         bHasDroppedRef = TRUE;
     260             :     }
     261           2 :     if (!oOutDBDatasetCache.empty())
     262             :     {
     263           0 :         oOutDBDatasetCache.clear();
     264           0 :         bHasDroppedRef = TRUE;
     265             :     }
     266             : 
     267           2 :     return bHasDroppedRef;
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                            FlushCache()                              */
     272             : /************************************************************************/
     273             : 
     274           2 : CPLErr PostGISRasterDataset::FlushCache(bool bAtClosing)
     275             : {
     276           2 :     const CPLErr eErr = VRTDataset::FlushCache(bAtClosing);
     277           2 :     oOutDBDatasetCache.clear();
     278           2 :     return eErr;
     279             : }
     280             : 
     281             : /************************************************************************/
     282             : /*                            HasSpatialIndex()                         */
     283             : /************************************************************************/
     284             : 
     285           0 : GBool PostGISRasterDataset::HasSpatialIndex()
     286             : {
     287           0 :     CPLString osCommand;
     288           0 :     PGresult *poResult = nullptr;
     289             : 
     290             :     // If exists, return it
     291           0 :     if (bHasTriedHasSpatialIndex)
     292             :     {
     293           0 :         return bHasSpatialIndex;
     294             :     }
     295             : 
     296           0 :     bHasTriedHasSpatialIndex = true;
     297             : 
     298             :     /* For debugging purposes only */
     299           0 :     if (CPLTestBool(CPLGetConfigOption("PR_DISABLE_GIST", "FALSE")))
     300           0 :         return false;
     301             : 
     302             :     // Copyright dustymugs !!!
     303             :     osCommand.Printf(
     304             :         "SELECT n.nspname AS schema_name, c2.relname AS table_name, "
     305             :         "att.attname AS column_name, "
     306             :         "       c.relname AS index_name, am.amname AS index_type "
     307             :         "FROM pg_catalog.pg_class c "
     308             :         "JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid "
     309             :         "JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid "
     310             :         "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
     311             :         "JOIN pg_am am ON c.relam = am.oid "
     312             :         "JOIN pg_attribute att ON att.attrelid = c2.oid "
     313             :         "AND pg_catalog.format_type(att.atttypid, att.atttypmod) = 'raster' "
     314             :         "WHERE c.relkind IN ('i') "
     315             :         "AND am.amname = 'gist' "
     316             :         "AND strpos(split_part(pg_catalog.pg_get_indexdef(i.indexrelid, 0, "
     317             :         "true), ' gist ', 2), att.attname) > 0 "
     318             :         "AND n.nspname = '%s' "
     319             :         "AND c2.relname = '%s' "
     320             :         "AND att.attname = '%s' ",
     321           0 :         pszSchema, pszTable, pszColumn);
     322             : 
     323             : #ifdef DEBUG_QUERY
     324             :     CPLDebug("PostGIS_Raster",
     325             :              "PostGISRasterDataset::HasSpatialIndex(): Query: %s",
     326             :              osCommand.c_str());
     327             : #endif
     328             : 
     329           0 :     poResult = PQexec(poConn, osCommand.c_str());
     330             : 
     331           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     332           0 :         PQntuples(poResult) <= 0)
     333             :     {
     334           0 :         bHasSpatialIndex = false;
     335           0 :         CPLDebug("PostGIS_Raster",
     336             :                  "For better performance, creating a spatial index "
     337             :                  "with 'CREATE INDEX %s_%s_%s_gist_idx ON %s.%s USING GIST "
     338             :                  "(ST_ConvexHull(%s))' is advised",
     339             :                  pszSchema, pszTable, pszColumn, pszSchema, pszTable,
     340             :                  pszColumn);
     341             :     }
     342             :     else
     343             :     {
     344           0 :         bHasSpatialIndex = true;
     345             :     }
     346             : 
     347           0 :     if (poResult)
     348           0 :         PQclear(poResult);
     349           0 :     return bHasSpatialIndex;
     350             : }
     351             : 
     352             : /***********************************************************************
     353             :  * \brief Look for a primary key in the table selected by the user
     354             :  *
     355             :  * If the table does not have a primary key, it returns NULL
     356             :  **********************************************************************/
     357           0 : const char *PostGISRasterDataset::GetPrimaryKeyRef()
     358             : {
     359           0 :     CPLString osCommand;
     360           0 :     PGresult *poResult = nullptr;
     361             : 
     362             :     // If exists, return it
     363           0 :     if (bHasTriedFetchingPrimaryKeyName)
     364             :     {
     365           0 :         return pszPrimaryKeyName;
     366             :     }
     367             : 
     368           0 :     bHasTriedFetchingPrimaryKeyName = true;
     369             : 
     370             :     /* For debugging purposes only */
     371           0 :     if (CPLTestBool(CPLGetConfigOption("PR_DISABLE_PK", "FALSE")))
     372           0 :         return nullptr;
     373             : 
     374             :     /* Determine the primary key/unique column on the table */
     375             :     osCommand.Printf(
     376             :         "select d.attname from pg_catalog.pg_constraint "
     377             :         "as a join pg_catalog.pg_indexes as b on a.conname = "
     378             :         "b.indexname join pg_catalog.pg_class as c on c.relname = "
     379             :         "b.tablename join pg_catalog.pg_attribute as d on "
     380             :         "c.relfilenode = d.attrelid where b.schemaname = '%s' and "
     381             :         "b.tablename = '%s' and d.attnum = a.conkey[1] and a.contype "
     382             :         "in ('p', 'u')",
     383           0 :         pszSchema, pszTable);
     384             : 
     385             : #ifdef DEBUG_QUERY
     386             :     CPLDebug("PostGIS_Raster",
     387             :              "PostGISRasterDataset::GetPrimaryKeyRef(): Query: %s",
     388             :              osCommand.c_str());
     389             : #endif
     390             : 
     391           0 :     poResult = PQexec(poConn, osCommand.c_str());
     392             : 
     393           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     394           0 :         PQntuples(poResult) <= 0)
     395             :     {
     396             : 
     397           0 :         PQclear(poResult);
     398             : 
     399             :         /**
     400             :          * Maybe there is no primary key or unique constraint; a
     401             :          * sequence will also suffice; get the first one
     402             :          **/
     403             : 
     404             :         osCommand.Printf(
     405             :             "select cols.column_name from "
     406             :             "information_schema.columns as cols join "
     407             :             "information_schema.sequences as seqs on "
     408             :             "cols.column_default like '%%'||seqs.sequence_name||'%%' "
     409             :             "where cols.table_schema = '%s' and cols.table_name = '%s'",
     410           0 :             pszSchema, pszTable);
     411             : 
     412             : #ifdef DEBUG_QUERY
     413             :         CPLDebug("PostGIS_Raster",
     414             :                  "PostGISRasterDataset::GetPrimaryKeyRef(): Query: %s",
     415             :                  osCommand.c_str());
     416             : #endif
     417             : 
     418           0 :         poResult = PQexec(poConn, osCommand.c_str());
     419             : 
     420           0 :         if (poResult == nullptr ||
     421           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     422           0 :             PQntuples(poResult) <= 0)
     423             :         {
     424             : 
     425           0 :             CPLDebug("PostGIS_Raster",
     426             :                      "PostGISRasterDataset::GetPrimaryKeyRef(): Could not "
     427             :                      "find a primary key or unique column on the specified "
     428             :                      "table %s.%s. For better performance, creating a primary "
     429             :                      "key on the table is advised.",
     430             :                      pszSchema, pszTable);
     431             : 
     432           0 :             pszPrimaryKeyName = nullptr;  // Just in case
     433             :         }
     434             :         else
     435             :         {
     436           0 :             pszPrimaryKeyName = CPLStrdup(PQgetvalue(poResult, 0, 0));
     437             :         }
     438             :     }
     439             : 
     440             :     // Ok, get the primary key
     441             :     else
     442             :     {
     443           0 :         pszPrimaryKeyName = CPLStrdup(PQgetvalue(poResult, 0, 0));
     444           0 :         bIsFastPK = true;
     445             :     }
     446             : 
     447           0 :     PQclear(poResult);
     448             : 
     449           0 :     return pszPrimaryKeyName;
     450             : }
     451             : 
     452             : /***********************************************************************
     453             :  * \brief Look for raster tables in database and store them as
     454             :  * subdatasets
     455             :  *
     456             :  * If no table is provided in connection string, the driver looks for
     457             :  * the existent raster tables in the schema given as argument. This
     458             :  * argument, however, is optional. If a NULL value is provided, the
     459             :  * driver looks for all raster tables in all schemas of the
     460             :  * user-provided database.
     461             :  *
     462             :  * NOTE: Permissions are managed by libpq. The driver only returns an
     463             :  * error if an error is returned when trying to access to tables not
     464             :  * allowed to the current user.
     465             :  **********************************************************************/
     466           0 : GBool PostGISRasterDataset::BrowseDatabase(const char *pszCurrentSchema,
     467             :                                            const char *pszValidConnectionString)
     468             : {
     469             : 
     470           0 :     char *l_pszSchema = nullptr;
     471           0 :     char *l_pszTable = nullptr;
     472           0 :     char *l_pszColumn = nullptr;
     473             : 
     474           0 :     int i = 0;
     475           0 :     int nTuples = 0;
     476           0 :     PGresult *poResult = nullptr;
     477           0 :     CPLString osCommand;
     478             : 
     479             :     /*************************************************************
     480             :      * Fetch all the raster tables and store them as subdatasets
     481             :      *************************************************************/
     482           0 :     if (pszCurrentSchema == nullptr)
     483             :     {
     484             :         osCommand.Printf(
     485             :             "select pg_namespace.nspname as schema, "
     486             :             "pg_class.relname as table, pg_attribute.attname as column "
     487             :             "from pg_class, pg_namespace,pg_attribute, pg_type where "
     488             :             "pg_class.relnamespace = pg_namespace.oid and "
     489             :             "pg_class.oid = pg_attribute.attrelid and "
     490             :             "pg_attribute.atttypid = pg_type.oid and "
     491           0 :             "pg_type.typname = 'raster'");
     492             : 
     493           0 :         poResult = PQexec(poConn, osCommand.c_str());
     494           0 :         if (poResult == nullptr ||
     495           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     496           0 :             PQntuples(poResult) <= 0)
     497             :         {
     498             : 
     499           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     500             :                         "Error browsing database for PostGIS Raster tables: %s",
     501           0 :                         PQerrorMessage(poConn));
     502           0 :             if (poResult != nullptr)
     503           0 :                 PQclear(poResult);
     504             : 
     505           0 :             return false;
     506             :         }
     507             : 
     508           0 :         nTuples = PQntuples(poResult);
     509           0 :         for (i = 0; i < nTuples; i++)
     510             :         {
     511           0 :             l_pszSchema = PQgetvalue(poResult, i, 0);
     512           0 :             l_pszTable = PQgetvalue(poResult, i, 1);
     513           0 :             l_pszColumn = PQgetvalue(poResult, i, 2);
     514             : 
     515           0 :             papszSubdatasets = CSLSetNameValue(
     516             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_NAME", (i + 1)),
     517             :                 CPLSPrintf("PG:%s schema='%s' table='%s' column='%s'",
     518             :                            pszValidConnectionString, l_pszSchema, l_pszTable,
     519             :                            l_pszColumn));
     520             : 
     521           0 :             papszSubdatasets = CSLSetNameValue(
     522             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_DESC", (i + 1)),
     523             :                 CPLSPrintf("PostGIS Raster table at %s.%s (%s)", l_pszSchema,
     524             :                            l_pszTable, l_pszColumn));
     525             :         }
     526             : 
     527           0 :         PQclear(poResult);
     528             :     }
     529             :     /***************************************************************
     530             :      * Fetch all the schema's raster tables and store them as
     531             :      * subdatasets
     532             :      **************************************************************/
     533             :     else
     534             :     {
     535             :         osCommand.Printf("select pg_class.relname as table, "
     536             :                          "pg_attribute.attname as column from pg_class, "
     537             :                          "pg_namespace,pg_attribute, pg_type where "
     538             :                          "pg_class.relnamespace = pg_namespace.oid and "
     539             :                          "pg_class.oid = pg_attribute.attrelid and "
     540             :                          "pg_attribute.atttypid = pg_type.oid and "
     541             :                          "pg_type.typname = 'raster' and "
     542             :                          "pg_namespace.nspname = '%s'",
     543           0 :                          pszCurrentSchema);
     544             : 
     545           0 :         poResult = PQexec(poConn, osCommand.c_str());
     546           0 :         if (poResult == nullptr ||
     547           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     548           0 :             PQntuples(poResult) <= 0)
     549             :         {
     550             : 
     551           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     552             :                         "Error browsing database for PostGIS Raster tables: %s",
     553           0 :                         PQerrorMessage(poConn));
     554             : 
     555           0 :             if (poResult != nullptr)
     556           0 :                 PQclear(poResult);
     557             : 
     558           0 :             return false;
     559             :         }
     560             : 
     561           0 :         nTuples = PQntuples(poResult);
     562           0 :         for (i = 0; i < nTuples; i++)
     563             :         {
     564           0 :             l_pszTable = PQgetvalue(poResult, i, 0);
     565           0 :             l_pszColumn = PQgetvalue(poResult, i, 1);
     566             : 
     567           0 :             papszSubdatasets = CSLSetNameValue(
     568             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_NAME", (i + 1)),
     569             :                 CPLSPrintf("PG:%s schema='%s' table='%s' column='%s'",
     570             :                            pszValidConnectionString, pszCurrentSchema,
     571             :                            l_pszTable, l_pszColumn));
     572             : 
     573           0 :             papszSubdatasets = CSLSetNameValue(
     574             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_DESC", (i + 1)),
     575             :                 CPLSPrintf("PostGIS Raster table at %s.%s (%s)",
     576             :                            pszCurrentSchema, l_pszTable, l_pszColumn));
     577             :         }
     578             : 
     579           0 :         PQclear(poResult);
     580             :     }
     581             : 
     582           0 :     return true;
     583             : }
     584             : 
     585             : /***********************************************************************
     586             :  * \brief Look for overview tables for the bands of the current dataset
     587             :  **********************************************************************/
     588           0 : PROverview *PostGISRasterDataset::GetOverviewTables(int *pnOverviews)
     589             : {
     590           0 :     PROverview *poOV = nullptr;
     591           0 :     CPLString osCommand;
     592             : 
     593             :     osCommand.Printf("SELECT o_table_name, overview_factor, "
     594             :                      "o_raster_column, o_table_schema FROM raster_overviews "
     595             :                      "WHERE r_table_schema = '%s' AND r_table_name = '%s' AND "
     596             :                      "r_raster_column = '%s' ORDER BY overview_factor",
     597           0 :                      this->pszSchema, this->pszTable, this->pszColumn);
     598             : 
     599             : #ifdef DEBUG_QUERY
     600             :     CPLDebug("PostGIS_Raster",
     601             :              "PostGISRasterDataset::GetOverviewTables(): Query: %s",
     602             :              osCommand.c_str());
     603             : #endif
     604             : 
     605           0 :     PGresult *poResult = PQexec(poConn, osCommand.c_str());
     606             : 
     607           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     608           0 :         PQntuples(poResult) < 0)
     609             :     {
     610             : 
     611           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     612             :                     "Error looking for overview tables: %s",
     613           0 :                     PQerrorMessage(poConn));
     614             : 
     615           0 :         if (poResult)
     616           0 :             PQclear(poResult);
     617             : 
     618           0 :         return nullptr;
     619             :     }
     620             : 
     621           0 :     else if (PQntuples(poResult) == 0)
     622             :     {
     623           0 :         CPLDebug("PostGIS_Raster",
     624             :                  "PostGISRasterDataset::GetOverviewTables(): No overviews "
     625             :                  "for table %s.%s",
     626             :                  pszTable, pszSchema);
     627             : 
     628           0 :         if (poResult)
     629           0 :             PQclear(poResult);
     630             : 
     631           0 :         return nullptr;
     632             :     }
     633             : 
     634           0 :     int nTuples = PQntuples(poResult);
     635             : 
     636           0 :     poOV = static_cast<PROverview *>(VSIMalloc2(nTuples, sizeof(PROverview)));
     637           0 :     if (poOV == nullptr)
     638             :     {
     639           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     640             :                     "Error looking for overview tables");
     641             : 
     642           0 :         PQclear(poResult);
     643             : 
     644           0 :         return nullptr;
     645             :     }
     646             : 
     647           0 :     int iOVerview = 0;
     648           0 :     for (iOVerview = 0; iOVerview < nTuples; iOVerview++)
     649             :     {
     650           0 :         poOV[iOVerview].pszSchema =
     651           0 :             CPLStrdup(PQgetvalue(poResult, iOVerview, 3));
     652             : 
     653           0 :         poOV[iOVerview].pszTable =
     654           0 :             CPLStrdup(PQgetvalue(poResult, iOVerview, 0));
     655             : 
     656           0 :         poOV[iOVerview].pszColumn =
     657           0 :             CPLStrdup(PQgetvalue(poResult, iOVerview, 2));
     658             : 
     659           0 :         poOV[iOVerview].nFactor = atoi(PQgetvalue(poResult, iOVerview, 1));
     660             :     }
     661             : 
     662           0 :     if (pnOverviews)
     663           0 :         *pnOverviews = nTuples;
     664             : 
     665           0 :     PQclear(poResult);
     666             : 
     667           0 :     return poOV;
     668             : }
     669             : 
     670             : /***********************************************************************
     671             :  * \brief Build overview datasets
     672             :  ***********************************************************************/
     673           0 : void PostGISRasterDataset::BuildOverviews()
     674             : {
     675           0 :     if (bHasBuiltOverviews || poParentDS != nullptr)
     676           0 :         return;
     677             : 
     678           0 :     bHasBuiltOverviews = true;
     679             :     /*******************************************************************
     680             :      * We also get the names of the overview tables, if they exist. So,
     681             :      * we'll can use them to create the overview datasets.
     682             :      ******************************************************************/
     683           0 :     int nOV = 0;
     684           0 :     PROverview *poOV = GetOverviewTables(&nOV);
     685             : 
     686           0 :     if (poOV)
     687             :     {
     688           0 :         papoOverviewDS = static_cast<PostGISRasterDataset **>(
     689           0 :             CPLCalloc(nOV, sizeof(PostGISRasterDataset *)));
     690           0 :         nOverviewCount = 0;
     691             : 
     692             :         int iOV;
     693           0 :         for (iOV = 0; iOV < nOV; iOV++)
     694             :         {
     695           0 :             PostGISRasterDataset *poOvrDS = new PostGISRasterDataset();
     696           0 :             poOvrDS->ShareLockWithParentDataset(this);
     697           0 :             poOvrDS->nOverviewFactor = poOV[iOV].nFactor;
     698           0 :             poOvrDS->poConn = poConn;
     699           0 :             poOvrDS->eAccess = eAccess;
     700           0 :             poOvrDS->eOutDBResolution = eOutDBResolution;
     701           0 :             poOvrDS->bHasStBandFileSize = bHasStBandFileSize;
     702           0 :             poOvrDS->nMode = nMode;
     703           0 :             poOvrDS->pszSchema = poOV[iOV].pszSchema;  // takes ownership
     704           0 :             poOvrDS->pszTable = poOV[iOV].pszTable;    // takes ownership
     705           0 :             poOvrDS->pszColumn = poOV[iOV].pszColumn;  // takes ownership
     706           0 :             poOvrDS->pszWhere = pszWhere ? CPLStrdup(pszWhere) : nullptr;
     707           0 :             poOvrDS->poParentDS = this;
     708             : 
     709           0 :             if (!CPLTestBool(
     710           0 :                     CPLGetConfigOption("PG_DEFERRED_OVERVIEWS", "YES")) &&
     711           0 :                 (!poOvrDS->SetRasterProperties(nullptr) ||
     712           0 :                  poOvrDS->GetRasterCount() != GetRasterCount()))
     713             :             {
     714           0 :                 delete poOvrDS;
     715             :             }
     716             :             else
     717             :             {
     718           0 :                 papoOverviewDS[nOverviewCount++] = poOvrDS;
     719             :             }
     720             :         }
     721             : 
     722           0 :         VSIFree(poOV);
     723             :     }
     724             : }
     725             : 
     726             : /***********************************************************************
     727             :  * \brief Returns overview count
     728             :  ***********************************************************************/
     729           0 : int PostGISRasterDataset::GetOverviewCount()
     730             : {
     731           0 :     BuildOverviews();
     732           0 :     return nOverviewCount;
     733             : }
     734             : 
     735             : /***********************************************************************
     736             :  * \brief Returns overview dataset
     737             :  ***********************************************************************/
     738           0 : PostGISRasterDataset *PostGISRasterDataset::GetOverviewDS(int iOvr)
     739             : {
     740           0 :     if (iOvr < 0 || iOvr > GetOverviewCount())
     741           0 :         return nullptr;
     742           0 :     return papoOverviewDS[iOvr];
     743             : }
     744             : 
     745             : /***********************************************************************
     746             :  * \brief Calculates the destination window for a VRT source, taking
     747             :  * into account that the source is a PostGIS Raster tile and the
     748             :  * destination is the general dataset itself
     749             :  *
     750             :  * This method is adapted from gdalbuildvrt as is in GDAL 1.10.0
     751             :  ***********************************************************************/
     752           0 : void PostGISRasterDataset::GetDstWin(PostGISRasterTileDataset *psDP,
     753             :                                      int *pnDstXOff, int *pnDstYOff,
     754             :                                      int *pnDstXSize, int *pnDstYSize)
     755             : {
     756           0 :     double we_res = this->adfGeoTransform[GEOTRSFRM_WE_RES];
     757           0 :     double ns_res = this->adfGeoTransform[GEOTRSFRM_NS_RES];
     758             : 
     759             :     double adfTileGeoTransform[6];
     760           0 :     psDP->GetGeoTransform(adfTileGeoTransform);
     761             : 
     762           0 :     *pnDstXOff = static_cast<int>(
     763           0 :         0.5 + (adfTileGeoTransform[GEOTRSFRM_TOPLEFT_X] - xmin) / we_res);
     764             : 
     765           0 :     if (ns_res < 0)
     766           0 :         *pnDstYOff = static_cast<int>(
     767           0 :             0.5 + (ymax - adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y]) / -ns_res);
     768             :     else
     769           0 :         *pnDstYOff = static_cast<int>(
     770           0 :             0.5 + (adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y] - ymin) / ns_res);
     771             : 
     772           0 :     *pnDstXSize = static_cast<int>(
     773           0 :         0.5 + psDP->GetRasterXSize() * adfTileGeoTransform[GEOTRSFRM_WE_RES] /
     774             :                   we_res);
     775           0 :     *pnDstYSize = static_cast<int>(
     776           0 :         0.5 + psDP->GetRasterYSize() * adfTileGeoTransform[GEOTRSFRM_NS_RES] /
     777             :                   ns_res);
     778           0 : }
     779             : 
     780             : /***********************************************************************
     781             :  * \brief Add tiles bands as complex source for raster bands.
     782             :  **********************************************************************/
     783           0 : void PostGISRasterDataset::AddComplexSource(PostGISRasterTileDataset *poRTDS)
     784             : {
     785             :     // Parameters to add the tile bands as sources
     786           0 :     int nDstXOff = 0;
     787           0 :     int nDstYOff = 0;
     788           0 :     int nDstXSize = 0;
     789           0 :     int nDstYSize = 0;
     790             : 
     791             :     // Get src and dst parameters
     792           0 :     GetDstWin(poRTDS, &nDstXOff, &nDstYOff, &nDstXSize, &nDstYSize);
     793             : 
     794             : #ifdef DEBUG_VERBOSE
     795             :     CPLDebug("PostGIS_Raster",
     796             :              "PostGISRasterDataset::AddComplexSource: "
     797             :              "Tile bounding box from (%d, %d) of size (%d, %d) will "
     798             :              "cover raster bounding box from (%d, %d) of size "
     799             :              "(%d, %d)",
     800             :              0, 0, poRTDS->GetRasterXSize(), poRTDS->GetRasterYSize(), nDstXOff,
     801             :              nDstYOff, nDstXSize, nDstYSize);
     802             : #endif
     803             : 
     804             :     // Add tiles bands as sources for the raster bands
     805           0 :     for (int iBand = 0; iBand < nBandsToCreate; iBand++)
     806             :     {
     807             :         PostGISRasterRasterBand *prb =
     808           0 :             cpl::down_cast<PostGISRasterRasterBand *>(GetRasterBand(iBand + 1));
     809             : 
     810           0 :         int bHasNoData = FALSE;
     811           0 :         double dfBandNoDataValue = prb->GetNoDataValue(&bHasNoData);
     812             : 
     813             :         PostGISRasterTileRasterBand *prtb =
     814           0 :             cpl::down_cast<PostGISRasterTileRasterBand *>(
     815             :                 poRTDS->GetRasterBand(iBand + 1));
     816             : 
     817           0 :         prb->AddComplexSource(
     818           0 :             prtb, 0, 0, poRTDS->GetRasterXSize(), poRTDS->GetRasterYSize(),
     819             :             nDstXOff, nDstYOff, nDstXSize, nDstYSize, 0.0, 1.0,
     820           0 :             (bHasNoData) ? dfBandNoDataValue : VRT_NODATA_UNSET);
     821             : 
     822           0 :         prtb->poSource = prb->papoSources[prb->nSources - 1];
     823             :     }
     824           0 : }
     825             : 
     826             : /************************************************************************/
     827             : /*                         GetMatchingSourceRef()                       */
     828             : /************************************************************************/
     829             : 
     830             : /**
     831             :  * \brief Get the tile dataset that matches the given upper left pixel
     832             :  **/
     833             : PostGISRasterTileDataset *
     834           0 : PostGISRasterDataset::GetMatchingSourceRef(double dfUpperLeftX,
     835             :                                            double dfUpperLeftY)
     836             : {
     837             :     int i;
     838           0 :     PostGISRasterTileDataset *poRTDS = nullptr;
     839             : 
     840           0 :     for (i = 0; i < m_nTiles; i++)
     841             :     {
     842           0 :         poRTDS = papoSourcesHolders[i];
     843             : 
     844           0 :         if (CPLIsEqual(poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_X],
     845           0 :                        dfUpperLeftX) &&
     846           0 :             CPLIsEqual(poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_Y],
     847             :                        dfUpperLeftY))
     848             :         {
     849             : 
     850           0 :             return poRTDS;
     851             :         }
     852             :     }
     853             : 
     854           0 :     return nullptr;
     855             : }
     856             : 
     857             : /************************************************************************/
     858             : /*                           CacheTile()                                */
     859             : /************************************************************************/
     860           0 : void PostGISRasterDataset::CacheTile(const char *pszMetadata,
     861             :                                      const char *pszRaster, const char *pszPKID,
     862             :                                      int nBand, bool bAllBandCaching)
     863             : {
     864             :     /**
     865             :      * Get metadata record and unpack it
     866             :      **/
     867           0 :     char *pszRes = CPLStrdup(pszMetadata);
     868             : 
     869             :     // Skip first "("
     870           0 :     char *pszFilteredRes = pszRes + 1;
     871             : 
     872             :     // Skip last ")"
     873           0 :     pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
     874             : 
     875             :     // Tokenize
     876           0 :     char **papszParams = CSLTokenizeString2(
     877             :         pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
     878           0 :     CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_METADATA_RECORD);
     879             : 
     880           0 :     CPLFree(pszRes);
     881             : 
     882           0 :     const double dfTileUpperLeftX = CPLAtof(papszParams[POS_UPPERLEFTX]);
     883           0 :     const double dfTileUpperLeftY = CPLAtof(papszParams[POS_UPPERLEFTY]);
     884           0 :     const double dfTileResX = CPLAtof(papszParams[POS_SCALEX]);
     885           0 :     const double dfTileResY = CPLAtof(papszParams[POS_SCALEY]);
     886           0 :     const int nTileXSize = atoi(papszParams[POS_WIDTH]);
     887           0 :     const int nTileYSize = atoi(papszParams[POS_HEIGHT]);
     888             : 
     889           0 :     CSLDestroy(papszParams);
     890           0 :     papszParams = nullptr;
     891             : 
     892             :     /**
     893             :      * Get actual raster band data
     894             :      **/
     895           0 :     const GDALDataType eDT = GetRasterBand(nBand)->GetRasterDataType();
     896           0 :     const int nBandDataTypeSize = GDALGetDataTypeSizeBytes(eDT);
     897           0 :     const int nExpectedBandDataSize =
     898           0 :         nTileXSize * nTileYSize * nBandDataTypeSize;
     899           0 :     const int nExpectedBands = bAllBandCaching ? GetRasterCount() : 1;
     900             : 
     901           0 :     int nWKBLength = 0;
     902             : 
     903             :     struct CPLFreer
     904             :     {
     905           0 :         void operator()(GByte *x) const
     906             :         {
     907           0 :             CPLFree(x);
     908           0 :         }
     909             :     };
     910             : 
     911             :     std::unique_ptr<GByte, CPLFreer> pbyDataAutoFreed(
     912           0 :         CPLHexToBinary(pszRaster, &nWKBLength));
     913           0 :     GByte *pbyData = pbyDataAutoFreed.get();
     914           0 :     const int nMinimumWKBLength =
     915           0 :         RASTER_HEADER_SIZE + BAND_SIZE(1, nBandDataTypeSize) * nExpectedBands;
     916           0 :     if (nWKBLength < nMinimumWKBLength)
     917             :     {
     918           0 :         CPLDebug("PostGIS_Raster",
     919             :                  "nWKBLength=%d. too short. Expected at least %d", nWKBLength,
     920             :                  nMinimumWKBLength);
     921           0 :         return;
     922             :     }
     923             : 
     924             :     // Do byte-swapping if necessary */
     925           0 :     const bool bIsLittleEndian = (pbyData[0] == 1);
     926             : #ifdef CPL_LSB
     927           0 :     const bool bSwap = !bIsLittleEndian;
     928             : #else
     929             :     const bool bSwap = bIsLittleEndian;
     930             : #endif
     931             : 
     932           0 :     PostGISRasterTileDataset *poRTDS = nullptr;
     933           0 :     if (GetPrimaryKeyRef() != nullptr)
     934           0 :         poRTDS = GetMatchingSourceRef(pszPKID);
     935             :     else
     936           0 :         poRTDS = GetMatchingSourceRef(dfTileUpperLeftX, dfTileUpperLeftY);
     937           0 :     if (poRTDS == nullptr)
     938             :     {
     939           0 :         return;
     940             :     }
     941             : 
     942           0 :     int nCurOffset = RASTER_HEADER_SIZE;
     943           0 :     for (int k = 1; k <= nExpectedBands; k++)
     944             :     {
     945             :         /**
     946             :          * Get the right PostGISRasterRasterBand
     947             :          **/
     948           0 :         int nCurBand = (nExpectedBands > 1) ? k : nBand;
     949             : 
     950             :         /**
     951             :          * Get the right tileband
     952             :          **/
     953           0 :         GDALRasterBand *poRTB = poRTDS->GetRasterBand(nCurBand);
     954           0 :         if (poRTB == nullptr)
     955           0 :             return;
     956             : 
     957             :         // For each band we have at least the flag byte, the nodata value
     958           0 :         if (nWKBLength < nCurOffset + 1 + nBandDataTypeSize)
     959             :         {
     960           0 :             CPLDebug("PostGIS_Raster",
     961             :                      "nWKBLength=%d, not enough data for band %d", nWKBLength,
     962             :                      k);
     963           0 :             return;
     964             :         }
     965             : 
     966             :         // Is it indb-raster ?
     967           0 :         if ((pbyData[nCurOffset] & 0x80) == 0)
     968             :         {
     969           0 :             nCurOffset += 1 + nBandDataTypeSize;
     970           0 :             if (nWKBLength < nCurOffset + nExpectedBandDataSize)
     971             :             {
     972           0 :                 CPLDebug("PostGIS_Raster",
     973             :                          "nWKBLength=%d, not enough data for band %d",
     974             :                          nWKBLength, k);
     975           0 :                 return;
     976             :             }
     977             : 
     978           0 :             GByte *pbyDataToRead = pbyData + nCurOffset;
     979           0 :             nCurOffset += nExpectedBandDataSize;
     980             : 
     981           0 :             if (bSwap && nBandDataTypeSize > 1)
     982             :             {
     983           0 :                 GDALSwapWords(pbyDataToRead, nBandDataTypeSize,
     984             :                               nTileXSize * nTileYSize, nBandDataTypeSize);
     985             :             }
     986             : 
     987             :             /**
     988             :              * Manually add each tile data to the cache of the
     989             :              * matching PostGISRasterTileRasterBand.
     990             :              **/
     991           0 :             GDALRasterBlock *poBlock = poRTB->GetLockedBlockRef(0, 0, TRUE);
     992           0 :             if (poBlock != nullptr)
     993             :             {
     994             :                 // Point block data ref to fetched data
     995           0 :                 memcpy(poBlock->GetDataRef(), pbyDataToRead,
     996             :                        nExpectedBandDataSize);
     997             : 
     998           0 :                 poBlock->DropLock();
     999             :             }
    1000             :         }
    1001             :         else
    1002             :         {
    1003             :             /**
    1004             :              * Manually add each tile data to the cache of the
    1005             :              * matching PostGISRasterTileRasterBand.
    1006             :              **/
    1007           0 :             GDALRasterBlock *poBlock = poRTB->GetLockedBlockRef(0, 0, TRUE);
    1008           0 :             if (poBlock == nullptr)
    1009           0 :                 return;
    1010           0 :             if (!LoadOutdbRaster(nCurOffset, eDT, k, pbyData, nWKBLength,
    1011             :                                  poBlock->GetDataRef(), dfTileUpperLeftX,
    1012             :                                  dfTileUpperLeftY, dfTileResX, dfTileResY,
    1013             :                                  nTileXSize, nTileYSize))
    1014             :             {
    1015           0 :                 poBlock->DropLock();
    1016           0 :                 return;
    1017             :             }
    1018           0 :             poBlock->DropLock();
    1019             :         }
    1020             :     }
    1021             : 
    1022           0 :     if (nCurOffset != nWKBLength)
    1023             :     {
    1024           0 :         CPLDebug("PostGIS_Raster",
    1025             :                  "Trailing bytes at end of serialized raster");
    1026           0 :         return;
    1027             :     }
    1028             : }
    1029             : 
    1030           0 : bool PostGISRasterDataset::LoadOutdbRaster(int &nCurOffset, GDALDataType eDT,
    1031             :                                            int nBand, const GByte *pbyData,
    1032             :                                            int nWKBLength, void *pImage,
    1033             :                                            double dfTileUpperLeftX,
    1034             :                                            double dfTileUpperLeftY,
    1035             :                                            double dfTileResX, double dfTileResY,
    1036             :                                            int nTileXSize, int nTileYSize)
    1037             : {
    1038           0 :     const int nBandDataTypeSize = GDALGetDataTypeSizeBytes(eDT);
    1039             : 
    1040           0 :     nCurOffset += 1 + nBandDataTypeSize;
    1041           0 :     if (nWKBLength < nCurOffset + 1 + 1)
    1042             :     {
    1043           0 :         CPLDebug("PostGIS_Raster", "nWKBLength=%d, not enough data for band %d",
    1044             :                  nWKBLength, nBand);
    1045           0 :         return false;
    1046             :     }
    1047             :     // Postgis raster outdb band numbering starts at 0
    1048           0 :     GByte nOutdbBandNumber = 1 + pbyData[nCurOffset];
    1049           0 :     nCurOffset++;
    1050           0 :     CPLString osPath;
    1051           0 :     for (int i = 0; nCurOffset + i < nWKBLength; i++)
    1052             :     {
    1053           0 :         if (pbyData[nCurOffset + i] == '\0')
    1054             :         {
    1055           0 :             osPath.assign(reinterpret_cast<const char *>(pbyData) + nCurOffset,
    1056           0 :                           i);
    1057           0 :             nCurOffset += i + 1;
    1058           0 :             break;
    1059             :         }
    1060             :     }
    1061           0 :     if (osPath.empty())
    1062             :     {
    1063           0 :         CPLDebug("PostGIS_Raster",
    1064             :                  "nWKBLength=%d, not enough data for outdb raster band %d",
    1065             :                  nWKBLength, nBand);
    1066           0 :         return false;
    1067             :     }
    1068             : #ifdef DEBUG_VERBOSE
    1069             :     CPLDebug("PostGIS_Raster", "Band %d: GDAL outdb band=%d %s", nBand,
    1070             :              nOutdbBandNumber, osPath.c_str());
    1071             : #endif
    1072           0 :     std::shared_ptr<GDALDataset> poDS;
    1073           0 :     if (!oOutDBDatasetCache.tryGet(osPath, poDS))
    1074             :     {
    1075           0 :         poDS.reset(GDALDataset::Open(osPath, GDAL_OF_RASTER));
    1076           0 :         if (poDS == nullptr)
    1077             :         {
    1078           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
    1079             :                      osPath.c_str());
    1080           0 :             return false;
    1081             :         }
    1082           0 :         oOutDBDatasetCache.insert(osPath, poDS);
    1083             :     }
    1084             : 
    1085           0 :     if (nOutdbBandNumber > poDS->GetRasterCount())
    1086             :     {
    1087           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid band number %d for %s",
    1088             :                  nOutdbBandNumber, osPath.c_str());
    1089           0 :         return false;
    1090             :     }
    1091             :     double adfGT[6];
    1092           0 :     poDS->GetGeoTransform(adfGT);
    1093           0 :     int nXOff =
    1094           0 :         static_cast<int>(std::round((dfTileUpperLeftX - adfGT[0]) / adfGT[1]));
    1095           0 :     int nYOff =
    1096           0 :         static_cast<int>(std::round((dfTileUpperLeftY - adfGT[3]) / adfGT[5]));
    1097           0 :     int nXOff2 = static_cast<int>(std::round(
    1098           0 :         (dfTileUpperLeftX + nTileXSize * dfTileResX - adfGT[0]) / adfGT[1]));
    1099           0 :     int nYOff2 = static_cast<int>(std::round(
    1100           0 :         (dfTileUpperLeftY + nTileYSize * dfTileResY - adfGT[3]) / adfGT[5]));
    1101           0 :     int nSrcXSize = nXOff2 - nXOff;
    1102           0 :     int nSrcYSize = nYOff2 - nYOff;
    1103           0 :     if (nXOff < 0 || nYOff < 0 || nXOff2 > poDS->GetRasterXSize() ||
    1104           0 :         nYOff2 > poDS->GetRasterYSize())
    1105             :     {
    1106           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1107             :                  "Requesting (%d,%d,%d,%d) in %dx%d raster", nXOff, nYOff,
    1108             :                  nSrcXSize, nSrcYSize, poDS->GetRasterXSize(),
    1109             :                  poDS->GetRasterYSize());
    1110           0 :         return false;
    1111             :     }
    1112             : 
    1113             :     // Point block data ref to fetched data
    1114             :     return poDS->GetRasterBand(nOutdbBandNumber)
    1115           0 :                ->RasterIO(GF_Read, nXOff, nYOff, nSrcXSize, nSrcYSize, pImage,
    1116             :                           nTileXSize, nTileYSize, eDT, 0, 0,
    1117           0 :                           nullptr) == CE_None;
    1118             : }
    1119             : 
    1120             : /************************************************************************/
    1121             : /*                          LoadSources()                               */
    1122             : /************************************************************************/
    1123             : 
    1124           0 : GBool PostGISRasterDataset::LoadSources(int nXOff, int nYOff, int nXSize,
    1125             :                                         int nYSize, int nBand)
    1126             : {
    1127           0 :     if (!bBuildQuadTreeDynamically)
    1128           0 :         return false;
    1129             : 
    1130           0 :     CPLString osSpatialFilter;
    1131           0 :     CPLString osIDsToFetch;
    1132           0 :     int nYSizeToQuery = nYSize;
    1133             : 
    1134           0 :     bool bFetchAll = false;
    1135           0 :     if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
    1136           0 :         nYSize == nRasterYSize)
    1137             :     {
    1138           0 :         bFetchAll = true;
    1139             :     }
    1140             :     else
    1141             :     {
    1142           0 :         if (nXOff >= m_nLastLoadSourcesXOff &&
    1143           0 :             nYOff >= m_nLastLoadSourcesYOff &&
    1144           0 :             nXOff + nXSize <=
    1145           0 :                 m_nLastLoadSourcesXOff + m_nLastLoadSourcesXSize &&
    1146           0 :             nYOff + nYSize <=
    1147           0 :                 m_nLastLoadSourcesYOff + m_nLastLoadSourcesYSize &&
    1148           0 :             nBand == m_nLastLoadSourcesBand)
    1149             :         {
    1150           0 :             return true;
    1151             :         }
    1152             : 
    1153             :         // To avoid doing too many small requests, try to query for at
    1154             :         // least 10 megapixels.
    1155           0 :         if (nXSize * nYSize < 10 * 1024 * 1024)
    1156             :         {
    1157           0 :             nYSizeToQuery = 10 * 1024 * 1024 / nXSize;
    1158           0 :             nYSizeToQuery = std::min(nYSizeToQuery, nRasterYSize - nYOff);
    1159           0 :             if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
    1160           0 :                 nYSize == nRasterYSize)
    1161             :             {
    1162           0 :                 bFetchAll = true;
    1163             :             }
    1164             :         }
    1165             :     }
    1166             : 
    1167           0 :     if (!bFetchAll)
    1168             :     {
    1169             :         double adfProjWin[8];
    1170           0 :         PolygonFromCoords(nXOff, nYOff, nXOff + nXSize, nYOff + nYSizeToQuery,
    1171             :                           adfProjWin);
    1172             :         osSpatialFilter.Printf("%s && "
    1173             :                         "ST_GeomFromText('POLYGON((%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f))') ",
    1174             :                         //"AND ST_Intersects(%s, ST_GeomFromEWKT('SRID=%d;POLYGON((%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f))'))",
    1175             :                         pszColumn,
    1176             :                         adfProjWin[0], adfProjWin[1],
    1177             :                         adfProjWin[2], adfProjWin[3],
    1178             :                         adfProjWin[4], adfProjWin[5],
    1179             :                         adfProjWin[6], adfProjWin[7],
    1180             :                         adfProjWin[0], adfProjWin[1]
    1181             :                         /*,pszColumn, nSrid,
    1182             :                         adfProjWin[0], adfProjWin[1],
    1183             :                         adfProjWin[2], adfProjWin[3],
    1184             :                         adfProjWin[4], adfProjWin[5],
    1185             :                         adfProjWin[6], adfProjWin[7],
    1186           0 :                         adfProjWin[0], adfProjWin[1]*/);
    1187             :     }
    1188             : 
    1189             :     bool bLoadRasters =
    1190           0 :         CPLTestBool(CPLGetConfigOption("PR_FORCE_LOAD_RASTERS", "FALSE"));
    1191           0 :     bool bAllBandCaching = false;
    1192             : 
    1193             :     const std::string osPrimaryKeyNameI(
    1194           0 :         CPLQuotedSQLIdentifier(pszPrimaryKeyName));
    1195           0 :     const std::string osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    1196           0 :     const std::string osTableI(CPLQuotedSQLIdentifier(pszTable));
    1197           0 :     const std::string osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    1198             : 
    1199           0 :     PGresult *poResult = nullptr;
    1200           0 :     if (m_nTiles > 0 && !bFetchAll)
    1201             :     {
    1202           0 :         CPLString osCommand;
    1203             :         osCommand.Printf("SELECT %s FROM %s.%s", osPrimaryKeyNameI.c_str(),
    1204           0 :                          osSchemaI.c_str(), osTableI.c_str());
    1205           0 :         osCommand += " WHERE ";
    1206           0 :         osCommand += osSpatialFilter;
    1207             : 
    1208           0 :         osSpatialFilter = "";
    1209             : 
    1210           0 :         poResult = PQexec(poConn, osCommand.c_str());
    1211             : 
    1212             : #ifdef DEBUG_QUERY
    1213             :         CPLDebug("PostGIS_Raster",
    1214             :                  "PostGISRasterDataset::LoadSources(): Query = \"%s\" --> "
    1215             :                  "number of rows = %d",
    1216             :                  osCommand.c_str(), poResult ? PQntuples(poResult) : 0);
    1217             : #endif
    1218             : 
    1219           0 :         if (poResult == nullptr ||
    1220           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1221           0 :             PQntuples(poResult) < 0)
    1222             :         {
    1223             : 
    1224           0 :             if (poResult)
    1225           0 :                 PQclear(poResult);
    1226             : 
    1227           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1228             :                      "PostGISRasterDataset::LoadSources(): %s",
    1229           0 :                      PQerrorMessage(poConn));
    1230             : 
    1231           0 :             return false;
    1232             :         }
    1233             : 
    1234           0 :         if (bTilesSameDimension && nBand > 0)
    1235             :         {
    1236             :             GIntBig nMemoryRequiredForTiles =
    1237           0 :                 static_cast<GIntBig>(PQntuples(poResult)) * nTileWidth *
    1238           0 :                 nTileHeight *
    1239           0 :                 (GDALGetDataTypeSize(
    1240           0 :                      GetRasterBand(nBand)->GetRasterDataType()) /
    1241           0 :                  8);
    1242           0 :             GIntBig nCacheMax = GDALGetCacheMax64();
    1243           0 :             if (nBands * nMemoryRequiredForTiles <= nCacheMax)
    1244             :             {
    1245           0 :                 bLoadRasters = true;
    1246           0 :                 bAllBandCaching = true;
    1247             :             }
    1248           0 :             else if (nMemoryRequiredForTiles <= nCacheMax)
    1249             :             {
    1250           0 :                 bLoadRasters = true;
    1251             :             }
    1252             :         }
    1253             : 
    1254             :         int i;
    1255           0 :         for (i = 0; i < PQntuples(poResult); i++)
    1256             :         {
    1257           0 :             const char *pszPKID = PQgetvalue(poResult, i, 0);
    1258           0 :             PostGISRasterTileDataset *poTile = GetMatchingSourceRef(pszPKID);
    1259           0 :             int bFetchTile = FALSE;
    1260           0 :             if (poTile == nullptr)
    1261           0 :                 bFetchTile = TRUE;
    1262           0 :             else if (bLoadRasters)
    1263             :             {
    1264             :                 PostGISRasterTileRasterBand *poTileBand =
    1265           0 :                     cpl::down_cast<PostGISRasterTileRasterBand *>(
    1266             :                         poTile->GetRasterBand(nBand));
    1267           0 :                 if (!poTileBand->IsCached())
    1268           0 :                     bFetchTile = TRUE;
    1269             :             }
    1270           0 :             if (bFetchTile)
    1271             :             {
    1272           0 :                 if (!osIDsToFetch.empty())
    1273           0 :                     osIDsToFetch += ",";
    1274           0 :                 osIDsToFetch += "'";
    1275           0 :                 osIDsToFetch += pszPKID;
    1276           0 :                 osIDsToFetch += "'";
    1277             :             }
    1278             :         }
    1279             : 
    1280           0 :         PQclear(poResult);
    1281             :     }
    1282             : 
    1283           0 :     if (bFetchAll || !osIDsToFetch.empty() || !osSpatialFilter.empty())
    1284             :     {
    1285           0 :         std::string osWHERE;
    1286           0 :         if (!osIDsToFetch.empty())
    1287             :         {
    1288           0 :             osWHERE += osPrimaryKeyNameI;
    1289           0 :             osWHERE += " IN (";
    1290           0 :             osWHERE += osIDsToFetch;
    1291           0 :             osWHERE += ")";
    1292             :         }
    1293           0 :         else if (!osSpatialFilter.empty())
    1294             :         {
    1295           0 :             osWHERE = std::move(osSpatialFilter);
    1296             :         }
    1297           0 :         if (pszWhere != nullptr)
    1298             :         {
    1299           0 :             if (!osWHERE.empty())
    1300           0 :                 osWHERE += " AND ";
    1301           0 :             osWHERE += "(";
    1302           0 :             osWHERE += pszWhere;
    1303           0 :             osWHERE += ")";
    1304             :         }
    1305             : 
    1306           0 :         bool bCanUseClientSide = true;
    1307           0 :         if (bLoadRasters &&
    1308           0 :             eOutDBResolution == OutDBResolution::CLIENT_SIDE_IF_POSSIBLE)
    1309             :         {
    1310             :             bCanUseClientSide =
    1311           0 :                 CanUseClientSideOutDB(bAllBandCaching, nBand, osWHERE);
    1312             :         }
    1313             : 
    1314           0 :         CPLString osCommand;
    1315             :         osCommand.Printf("SELECT %s, ST_Metadata(%s)",
    1316           0 :                          osPrimaryKeyNameI.c_str(), osColumnI.c_str());
    1317           0 :         if (bLoadRasters)
    1318             :         {
    1319           0 :             CPLString orRasterToFetch;
    1320           0 :             if (bAllBandCaching)
    1321             :             {
    1322           0 :                 orRasterToFetch = osColumnI;
    1323             :             }
    1324             :             else
    1325             :             {
    1326             :                 orRasterToFetch.Printf("ST_Band(%s, %d)", osColumnI.c_str(),
    1327           0 :                                        nBand);
    1328             :             }
    1329           0 :             if (eOutDBResolution == OutDBResolution::SERVER_SIDE ||
    1330           0 :                 !bCanUseClientSide)
    1331             :             {
    1332             :                 orRasterToFetch =
    1333           0 :                     "encode(ST_AsBinary(" + orRasterToFetch + ",TRUE),'hex')";
    1334             :             }
    1335           0 :             osCommand += ", " + orRasterToFetch;
    1336             :         }
    1337             :         osCommand +=
    1338           0 :             CPLSPrintf(" FROM %s.%s", osSchemaI.c_str(), osTableI.c_str());
    1339           0 :         if (!osWHERE.empty())
    1340             :         {
    1341           0 :             osCommand += " WHERE ";
    1342           0 :             osCommand += osWHERE;
    1343             :         }
    1344             : 
    1345           0 :         poResult = PQexec(poConn, osCommand.c_str());
    1346             : 
    1347             : #ifdef DEBUG_QUERY
    1348             :         CPLDebug("PostGIS_Raster",
    1349             :                  "PostGISRasterDataset::LoadSources(): Query = \"%s\" --> "
    1350             :                  "number of rows = %d",
    1351             :                  osCommand.c_str(), poResult ? PQntuples(poResult) : 0);
    1352             : #endif
    1353             : 
    1354           0 :         if (poResult == nullptr ||
    1355           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1356           0 :             PQntuples(poResult) < 0)
    1357             :         {
    1358             : 
    1359           0 :             if (poResult)
    1360           0 :                 PQclear(poResult);
    1361             : 
    1362           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1363             :                      "PostGISRasterDataset::LoadSources(): %s",
    1364           0 :                      PQerrorMessage(poConn));
    1365             : 
    1366           0 :             return false;
    1367             :         }
    1368             : 
    1369           0 :         for (int i = 0; i < PQntuples(poResult); i++)
    1370             :         {
    1371           0 :             const char *pszPKID = PQgetvalue(poResult, i, 0);
    1372           0 :             const char *pszMetadata = PQgetvalue(poResult, i, 1);
    1373             : 
    1374           0 :             PostGISRasterTileDataset *poRTDS = GetMatchingSourceRef(pszPKID);
    1375           0 :             if (poRTDS == nullptr)
    1376             :             {
    1377           0 :                 poRTDS = BuildRasterTileDataset(pszMetadata, pszPKID,
    1378             :                                                 GetRasterCount(), nullptr);
    1379           0 :                 if (poRTDS != nullptr)
    1380             :                 {
    1381           0 :                     AddComplexSource(poRTDS);
    1382             : 
    1383           0 :                     oMapPKIDToRTDS[poRTDS->pszPKID] = poRTDS;
    1384           0 :                     papoSourcesHolders =
    1385             :                         static_cast<PostGISRasterTileDataset **>(
    1386           0 :                             CPLRealloc(papoSourcesHolders,
    1387             :                                        sizeof(PostGISRasterTileDataset *) *
    1388           0 :                                            (m_nTiles + 1)));
    1389           0 :                     papoSourcesHolders[m_nTiles++] = poRTDS;
    1390           0 :                     CPLQuadTreeInsert(hQuadTree, poRTDS);
    1391             :                 }
    1392             :             }
    1393             : 
    1394           0 :             if (bLoadRasters && poRTDS != nullptr)
    1395             :             {
    1396           0 :                 const char *pszRaster = PQgetvalue(poResult, i, 2);
    1397           0 :                 CacheTile(pszMetadata, pszRaster, pszPKID, nBand,
    1398             :                           bAllBandCaching);
    1399             :             }
    1400             :         }
    1401             : 
    1402           0 :         PQclear(poResult);
    1403             :     }
    1404             : 
    1405             :     // If we have fetched the surface of all the dataset, then all sources have
    1406             :     // been built, and we don't need to do a spatial query on following
    1407             :     // IRasterIO() calls
    1408           0 :     if (bFetchAll)
    1409           0 :         bBuildQuadTreeDynamically = false;
    1410             : 
    1411           0 :     m_nLastLoadSourcesXOff = nXOff;
    1412           0 :     m_nLastLoadSourcesYOff = nYOff;
    1413           0 :     m_nLastLoadSourcesXSize = nXSize;
    1414           0 :     m_nLastLoadSourcesYSize = nYSizeToQuery;
    1415           0 :     m_nLastLoadSourcesBand = nBand;
    1416             : 
    1417           0 :     return true;
    1418             : }
    1419             : 
    1420             : /***********************************************************************
    1421             :  * \brief Determine if the tiles satisfying a request use outdb rasters
    1422             :  *        that can be resolved client-side.
    1423             :  **********************************************************************/
    1424           0 : bool PostGISRasterDataset::CanUseClientSideOutDB(bool bAllBandCaching,
    1425             :                                                  int nBand,
    1426             :                                                  const CPLString &osWHERE)
    1427             : {
    1428           0 :     CPLString osCommand;
    1429           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    1430           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    1431           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    1432             : 
    1433           0 :     if (bAllBandCaching)
    1434             :     {
    1435           0 :         if (bHasStBandFileSize)
    1436             :         {
    1437             :             osCommand.Printf(
    1438             :                 "SELECT DISTINCT ST_BandPath(%s,band), "
    1439             :                 "ST_BandFileSize(%s,band), ST_BandFileTimeStamp(%s,band) FROM "
    1440             :                 "(SELECT %s, generate_series(1, ST_NumBands(%s)) band FROM "
    1441             :                 "%s.%s%s) foo",
    1442             :                 osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    1443             :                 osColumnI.c_str(), osColumnI.c_str(), osSchemaI.c_str(),
    1444             :                 osTableI.c_str(),
    1445           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1446             :         }
    1447             :         else
    1448             :         {
    1449             :             osCommand.Printf(
    1450             :                 "SELECT DISTINCT ST_BandPath(%s,band) FROM "
    1451             :                 "(SELECT %s, generate_series(1, ST_NumBands(%s)) band FROM "
    1452             :                 "%s.%s%s) foo",
    1453             :                 osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    1454             :                 osSchemaI.c_str(), osTableI.c_str(),
    1455           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1456             :         }
    1457             :     }
    1458             :     else
    1459             :     {
    1460           0 :         if (bHasStBandFileSize)
    1461             :         {
    1462             :             osCommand.Printf(
    1463             :                 "SELECT DISTINCT ST_BandPath(%s,%d), "
    1464             :                 "ST_BandFileSize(%s,%d), ST_BandFileTimeStamp(%s,%d) "
    1465             :                 "FROM %s.%s%s",
    1466             :                 osColumnI.c_str(), nBand, osColumnI.c_str(), nBand,
    1467             :                 osColumnI.c_str(), nBand, osSchemaI.c_str(), osTableI.c_str(),
    1468           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1469             :         }
    1470             :         else
    1471             :         {
    1472             :             osCommand.Printf(
    1473             :                 "SELECT DISTINCT ST_BandPath(%s,%d) FROM %s.%s%s",
    1474             :                 osColumnI.c_str(), nBand, osSchemaI.c_str(), osTableI.c_str(),
    1475           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1476             :         }
    1477             :     }
    1478           0 :     PGresult *poResult = PQexec(poConn, osCommand.c_str());
    1479             : #ifdef DEBUG_QUERY
    1480             :     CPLDebug("PostGIS_Raster",
    1481             :              "PostGISRasterRasterBand::CanUseClientSideOutDB(): "
    1482             :              "Query = \"%s\" --> number of rows = %d",
    1483             :              osCommand.c_str(), poResult ? PQntuples(poResult) : 0);
    1484             : #endif
    1485             : 
    1486           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1487           0 :         PQntuples(poResult) < 0)
    1488             :     {
    1489             : 
    1490           0 :         if (poResult)
    1491           0 :             PQclear(poResult);
    1492             : 
    1493           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1494             :                  "PostGISRasterRasterBand::CanUseClientSideOutDB(): %s",
    1495           0 :                  PQerrorMessage(poConn));
    1496             : 
    1497           0 :         return false;
    1498             :     }
    1499           0 :     bool bCanUseClientSide = true;
    1500           0 :     const int nTuples = PQntuples(poResult);
    1501           0 :     for (int i = 0; i < nTuples; i++)
    1502             :     {
    1503           0 :         const char *pszFilename = PQgetvalue(poResult, i, 0);
    1504           0 :         if (pszFilename)
    1505             :         {
    1506           0 :             bool bUsable = false;
    1507           0 :             if (!oOutDBFilenameUsable.tryGet(std::string(pszFilename), bUsable))
    1508             :             {
    1509             :                 VSIStatBufL sStat;
    1510           0 :                 bUsable = (VSIStatL(pszFilename, &sStat) == 0);
    1511           0 :                 if (bUsable && bHasStBandFileSize)
    1512             :                 {
    1513           0 :                     const char *pszSize = PQgetvalue(poResult, i, 1);
    1514           0 :                     const char *pszTimestamp = PQgetvalue(poResult, i, 2);
    1515           0 :                     if (pszSize && pszTimestamp)
    1516             :                     {
    1517           0 :                         bUsable &=
    1518           0 :                             (static_cast<GUInt64>(CPLAtoGIntBig(pszSize)) ==
    1519           0 :                              static_cast<GUInt64>(sStat.st_size));
    1520           0 :                         bUsable &= (static_cast<GUInt64>(
    1521           0 :                                         CPLAtoGIntBig(pszTimestamp)) ==
    1522           0 :                                     static_cast<GUInt64>(sStat.st_mtime));
    1523             :                     }
    1524             :                 }
    1525           0 :                 oOutDBFilenameUsable.insert(std::string(pszFilename), bUsable);
    1526             :             }
    1527           0 :             if (!bUsable)
    1528             :             {
    1529           0 :                 CPLDebug("PostGIS_Raster",
    1530             :                          "File %s not usable from client side", pszFilename);
    1531           0 :                 bCanUseClientSide = false;
    1532             :             }
    1533             :         }
    1534             :     }
    1535           0 :     PQclear(poResult);
    1536           0 :     return bCanUseClientSide;
    1537             : }
    1538             : 
    1539             : /***********************************************************************
    1540             :  * \brief Get some useful metadata for all bands
    1541             :  *
    1542             :  * The allocated memory is responsibility of the caller
    1543             :  **********************************************************************/
    1544           0 : BandMetadata *PostGISRasterDataset::GetBandsMetadata(int *pnBands)
    1545             : {
    1546           0 :     BandMetadata *poBMD = nullptr;
    1547           0 :     PGresult *poResult = nullptr;
    1548           0 :     CPLString osCommand;
    1549           0 :     char *pszRes = nullptr;
    1550           0 :     char *pszFilteredRes = nullptr;
    1551           0 :     char **papszParams = nullptr;
    1552             : 
    1553           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    1554           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    1555           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    1556             : 
    1557             :     osCommand.Printf("select st_bandmetadata(%s, band) from "
    1558             :                      "(select %s, generate_series(1, %d) band from "
    1559             :                      "(select %s from %s.%s where (%s) AND st_numbands(%s)=%d "
    1560             :                      "limit 1) bar) foo",
    1561             :                      osColumnI.c_str(), osColumnI.c_str(), nBandsToCreate,
    1562             :                      osColumnI.c_str(), osSchemaI.c_str(), osTableI.c_str(),
    1563           0 :                      pszWhere ? pszWhere : "true", osColumnI.c_str(),
    1564           0 :                      nBandsToCreate);
    1565             : 
    1566             : #ifdef DEBUG_QUERY
    1567             :     CPLDebug("PostGIS_Raster",
    1568             :              "PostGISRasterDataset::GetBandsMetadata(): Query: %s",
    1569             :              osCommand.c_str());
    1570             : #endif
    1571             : 
    1572           0 :     poResult = PQexec(poConn, osCommand.c_str());
    1573             :     /* Error getting info from database */
    1574           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1575           0 :         PQntuples(poResult) <= 0)
    1576             :     {
    1577             : 
    1578           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    1579             :                     "Error getting band metadata while creating raster "
    1580             :                     "bands");
    1581             : 
    1582           0 :         CPLDebug("PostGIS_Raster",
    1583             :                  "PostGISRasterDataset::GetBandsMetadata(): %s",
    1584           0 :                  PQerrorMessage(poConn));
    1585             : 
    1586           0 :         if (poResult)
    1587           0 :             PQclear(poResult);
    1588             : 
    1589           0 :         return nullptr;
    1590             :     }
    1591             : 
    1592             :     // Matches nBands
    1593           0 :     int nTuples = PQntuples(poResult);
    1594             : 
    1595             :     poBMD = static_cast<BandMetadata *>(
    1596           0 :         VSI_MALLOC2_VERBOSE(nTuples, sizeof(BandMetadata)));
    1597           0 :     if (poBMD == nullptr)
    1598             :     {
    1599           0 :         PQclear(poResult);
    1600             : 
    1601           0 :         return nullptr;
    1602             :     }
    1603             : 
    1604           0 :     int iBand = 0;
    1605             : 
    1606           0 :     for (iBand = 0; iBand < nTuples; iBand++)
    1607             :     {
    1608             : 
    1609             :         // Get metadata record
    1610           0 :         pszRes = CPLStrdup(PQgetvalue(poResult, iBand, 0));
    1611             : 
    1612             :         // Skip first "("
    1613           0 :         pszFilteredRes = pszRes + 1;
    1614             : 
    1615             :         // Skip last ")"
    1616           0 :         pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
    1617             : 
    1618             :         // Tokenize
    1619           0 :         papszParams = CSLTokenizeString2(
    1620             :             pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
    1621           0 :         CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_BAND_METADATA_RECORD);
    1622             : 
    1623           0 :         CPLFree(pszRes);
    1624             : 
    1625             :         // If the band doesn't have nodata, NULL is returned as nodata
    1626           0 :         TranslateDataType(papszParams[POS_PIXELTYPE], &(poBMD[iBand].eDataType),
    1627           0 :                           &(poBMD[iBand].nBitsDepth));
    1628             : 
    1629           0 :         if (papszParams[POS_NODATAVALUE] == nullptr ||
    1630           0 :             EQUAL(papszParams[POS_NODATAVALUE], "NULL") ||
    1631           0 :             EQUAL(papszParams[POS_NODATAVALUE], "f") ||
    1632           0 :             EQUAL(papszParams[POS_NODATAVALUE], ""))
    1633             :         {
    1634             : 
    1635           0 :             poBMD[iBand].bHasNoDataValue = false;
    1636           0 :             poBMD[iBand].dfNoDataValue = CPLAtof(NO_VALID_RES);
    1637             :         }
    1638             : 
    1639             :         else
    1640             :         {
    1641           0 :             poBMD[iBand].bHasNoDataValue = true;
    1642           0 :             poBMD[iBand].dfNoDataValue = CPLAtof(papszParams[POS_NODATAVALUE]);
    1643             :         }
    1644             : 
    1645           0 :         poBMD[iBand].bIsOffline = (papszParams[POS_ISOUTDB] != nullptr)
    1646           0 :                                       ? EQUAL(papszParams[POS_ISOUTDB], "t")
    1647             :                                       : false;
    1648             : 
    1649           0 :         CSLDestroy(papszParams);
    1650             :     }
    1651             : 
    1652           0 :     if (pnBands)
    1653           0 :         *pnBands = nTuples;
    1654             : 
    1655           0 :     PQclear(poResult);
    1656             : 
    1657           0 :     return poBMD;
    1658             : }
    1659             : 
    1660             : /***********************************************************************
    1661             :  * \brief Function to get the bounding box of each element inserted in
    1662             :  * the QuadTree index
    1663             :  **********************************************************************/
    1664           0 : static void GetTileBoundingBox(const void *hFeature, CPLRectObj *pBounds)
    1665             : {
    1666           0 :     PostGISRasterTileDataset *poRTD = const_cast<PostGISRasterTileDataset *>(
    1667             :         reinterpret_cast<const PostGISRasterTileDataset *>(hFeature));
    1668             : 
    1669             :     double adfTileGeoTransform[6];
    1670           0 :     poRTD->GetGeoTransform(adfTileGeoTransform);
    1671             : 
    1672           0 :     int nTileWidth = poRTD->GetRasterXSize();
    1673           0 :     int nTileHeight = poRTD->GetRasterYSize();
    1674             : 
    1675           0 :     pBounds->minx = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_X];
    1676           0 :     pBounds->maxx = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_X] +
    1677           0 :                     nTileWidth * adfTileGeoTransform[GEOTRSFRM_WE_RES];
    1678             : 
    1679           0 :     if (adfTileGeoTransform[GEOTRSFRM_NS_RES] >= 0.0)
    1680             :     {
    1681           0 :         pBounds->miny = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y];
    1682           0 :         pBounds->maxy = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    1683           0 :                         nTileHeight * adfTileGeoTransform[GEOTRSFRM_NS_RES];
    1684             :     }
    1685             :     else
    1686             :     {
    1687           0 :         pBounds->maxy = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y];
    1688           0 :         pBounds->miny = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    1689           0 :                         nTileHeight * adfTileGeoTransform[GEOTRSFRM_NS_RES];
    1690             :     }
    1691             : 
    1692             : #ifdef DEBUG_VERBOSE
    1693             :     CPLDebug("PostGIS_Raster",
    1694             :              "TileBoundingBox minx=%f miny=%f maxx=%f maxy=%f "
    1695             :              "adfTileGeoTransform[GEOTRSFRM_NS_RES]=%f",
    1696             :              pBounds->minx, pBounds->miny, pBounds->maxx, pBounds->maxy,
    1697             :              adfTileGeoTransform[GEOTRSFRM_NS_RES]);
    1698             : #endif
    1699             : 
    1700           0 :     return;
    1701             : }
    1702             : 
    1703             : /********************************************************
    1704             :  * \brief Builds a PostGISRasterTileDataset* object from the ST_Metadata
    1705             :  ********************************************************/
    1706           0 : PostGISRasterTileDataset *PostGISRasterDataset::BuildRasterTileDataset(
    1707             :     const char *pszMetadata, const char *pszPKID, int nBandsFetched,
    1708             :     BandMetadata *poBandMetaData)
    1709             : {
    1710             :     // Get metadata record
    1711           0 :     char *pszRes = CPLStrdup(pszMetadata);
    1712             : 
    1713             :     // Skip first "("
    1714           0 :     char *pszFilteredRes = pszRes + 1;
    1715             : 
    1716             :     // Skip last ")"
    1717           0 :     pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
    1718             : 
    1719             :     // Tokenize
    1720           0 :     char **papszParams = CSLTokenizeString2(
    1721             :         pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
    1722           0 :     CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_METADATA_RECORD);
    1723             : 
    1724           0 :     CPLFree(pszRes);
    1725             : 
    1726           0 :     double tileSkewX = CPLAtof(papszParams[POS_SKEWX]);
    1727           0 :     double tileSkewY = CPLAtof(papszParams[POS_SKEWY]);
    1728             : 
    1729             :     // Rotated rasters are not allowed, so far
    1730             :     // TODO: allow them
    1731           0 :     if (!CPLIsEqual(tileSkewX, 0.0) || !CPLIsEqual(tileSkewY, 0.0))
    1732             :     {
    1733             : 
    1734           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    1735             :                     "GDAL PostGIS Raster driver can not work with "
    1736             :                     "rotated rasters yet.");
    1737             : 
    1738           0 :         CSLDestroy(papszParams);
    1739           0 :         return nullptr;
    1740             :     }
    1741             : 
    1742           0 :     int l_nTileWidth = atoi(papszParams[POS_WIDTH]);
    1743           0 :     int l_nTileHeight = atoi(papszParams[POS_HEIGHT]);
    1744             : 
    1745             :     /**
    1746             :      * Now, construct a PostGISRasterTileDataset, and add
    1747             :      * its bands as sources for the general raster bands
    1748             :      **/
    1749           0 :     int nTileBands = atoi(papszParams[POS_NBANDS]);
    1750             : 
    1751             :     /**
    1752             :      * If the source doesn't have the same number of bands than
    1753             :      * the raster band, is discarded
    1754             :      **/
    1755           0 :     if (nTileBands != nBandsFetched)
    1756             :     {
    1757           0 :         CPLDebug("PostGIS_Raster",
    1758             :                  "PostGISRasterDataset::"
    1759             :                  "BuildRasterTileDataset(): Tile has %d "
    1760             :                  "bands, and the raster has %d bands. Discarding "
    1761             :                  "this tile",
    1762             :                  nTileBands, nBandsFetched);
    1763             : 
    1764           0 :         CSLDestroy(papszParams);
    1765             : 
    1766           0 :         return nullptr;
    1767             :     }
    1768             : 
    1769             :     PostGISRasterTileDataset *poRTDS =
    1770           0 :         new PostGISRasterTileDataset(this, l_nTileWidth, l_nTileHeight);
    1771           0 :     poRTDS->ShareLockWithParentDataset(this);
    1772             : 
    1773           0 :     if (GetPrimaryKeyRef() != nullptr)
    1774             :     {
    1775           0 :         poRTDS->pszPKID = CPLStrdup(pszPKID);
    1776             :     }
    1777             : 
    1778           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_X] =
    1779           0 :         CPLAtof(papszParams[POS_UPPERLEFTX]);
    1780             : 
    1781           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] =
    1782           0 :         CPLAtof(papszParams[POS_UPPERLEFTY]);
    1783             : 
    1784           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_WE_RES] =
    1785           0 :         CPLAtof(papszParams[POS_SCALEX]);
    1786             : 
    1787           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_NS_RES] =
    1788           0 :         CPLAtof(papszParams[POS_SCALEY]);
    1789             : 
    1790           0 :     for (int j = 0; j < nTileBands; j++)
    1791             :     {
    1792             : 
    1793             :         // Create band
    1794           0 :         poRTDS->SetBand(j + 1,
    1795             :                         new PostGISRasterTileRasterBand(
    1796             :                             poRTDS, j + 1,
    1797             :                             (poBandMetaData)
    1798           0 :                                 ? poBandMetaData[j].eDataType
    1799           0 :                                 : GetRasterBand(j + 1)->GetRasterDataType()));
    1800             :     }
    1801             : 
    1802           0 :     CSLDestroy(papszParams);
    1803             : 
    1804           0 :     return poRTDS;
    1805             : }
    1806             : 
    1807             : /********************************************************
    1808             :  * \brief Updates components GEOTRSFRM_WE_RES and GEOTRSFRM_NS_RES
    1809             :  *        of dataset adfGeoTransform
    1810             :  ********************************************************/
    1811           0 : void PostGISRasterDataset::UpdateGlobalResolutionWithTileResolution(
    1812             :     double tilePixelSizeX, double tilePixelSizeY)
    1813             : {
    1814             :     // Calculate pixel size
    1815           0 :     if (resolutionStrategy == AVERAGE_RESOLUTION ||
    1816           0 :         resolutionStrategy == AVERAGE_APPROX_RESOLUTION)
    1817             :     {
    1818           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] += tilePixelSizeX;
    1819           0 :         adfGeoTransform[GEOTRSFRM_NS_RES] += tilePixelSizeY;
    1820             :     }
    1821             : 
    1822           0 :     else if (resolutionStrategy == HIGHEST_RESOLUTION)
    1823             :     {
    1824           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] =
    1825           0 :             std::min(adfGeoTransform[GEOTRSFRM_WE_RES], tilePixelSizeX);
    1826             : 
    1827             :         /**
    1828             :          * Yes : as ns_res is negative, the highest resolution
    1829             :          * is the max value.
    1830             :          *
    1831             :          * Negative tilePixelSizeY means that the coords origin
    1832             :          * is in top left corner. This is not the common
    1833             :          * situation. Most image files store data from top to
    1834             :          * bottom, while the projected coordinate systems
    1835             :          * utilize traditional Cartesian coordinates with the
    1836             :          * origin in the conventional lower-left corner (bottom
    1837             :          * to top). For that reason, this parameter is normally
    1838             :          * negative.
    1839             :          **/
    1840           0 :         if (tilePixelSizeY < 0.0)
    1841           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1842           0 :                 std::max(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1843             :         else
    1844           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1845           0 :                 std::min(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1846             :     }
    1847             : 
    1848           0 :     else if (resolutionStrategy == LOWEST_RESOLUTION)
    1849             :     {
    1850           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] =
    1851           0 :             std::max(adfGeoTransform[GEOTRSFRM_WE_RES], tilePixelSizeX);
    1852             : 
    1853           0 :         if (tilePixelSizeY < 0.0)
    1854           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1855           0 :                 std::min(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1856             :         else
    1857           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1858           0 :                 std::max(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1859             :     }
    1860           0 : }
    1861             : 
    1862             : /***********************************************************************
    1863             :  * \brief Build bands
    1864             :  ***********************************************************************/
    1865           0 : void PostGISRasterDataset::BuildBands(BandMetadata *poBandMetaData,
    1866             :                                       int nBandsFetched)
    1867             : {
    1868             : #ifdef DEBUG_VERBOSE
    1869             :     CPLDebug("PostGIS_Raster",
    1870             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    1871             :              "Now constructing the raster dataset bands");
    1872             : #endif
    1873             : 
    1874             :     int iBand;
    1875           0 :     for (iBand = 0; iBand < nBandsFetched; iBand++)
    1876             :     {
    1877             : 
    1878           0 :         SetBand(iBand + 1, new PostGISRasterRasterBand(
    1879           0 :                                this, iBand + 1, poBandMetaData[iBand].eDataType,
    1880           0 :                                poBandMetaData[iBand].bHasNoDataValue,
    1881           0 :                                poBandMetaData[iBand].dfNoDataValue));
    1882             : 
    1883             :         // Set some band metadata items
    1884           0 :         GDALRasterBand *b = GetRasterBand(iBand + 1);
    1885           0 :         if (poBandMetaData[iBand].nBitsDepth < 8)
    1886             :         {
    1887           0 :             b->SetMetadataItem(
    1888             :                 "NBITS",
    1889           0 :                 CPLString().Printf("%d", poBandMetaData[iBand].nBitsDepth),
    1890           0 :                 "IMAGE_STRUCTURE");
    1891             :         }
    1892             : 
    1893             : #ifdef DEBUG_VERBOSE
    1894             :         CPLDebug("PostGIS_Raster",
    1895             :                  "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    1896             :                  "Band %d built",
    1897             :                  iBand + 1);
    1898             : #endif
    1899             :     }
    1900           0 : }
    1901             : 
    1902             : /***********************************************************************
    1903             :  * \brief Construct just one dataset from all the results fetched.
    1904             :  *
    1905             :  * This method is not very elegant. It is strongly attached to
    1906             :  * SetRasterProperties (it assumes poResult is not NULL, and the actual
    1907             :  * results are stored at fixed positions). I just did it to avoid a
    1908             :  * huge SetRasterProperties method.
    1909             :  *
    1910             :  * I know, this could be avoided in a better way. Like implementing a
    1911             :  * wrapper to raise queries and get results without all the checking
    1912             :  * overhead. I'd like to do it, someday...
    1913             :  **********************************************************************/
    1914           0 : GBool PostGISRasterDataset::ConstructOneDatasetFromTiles(PGresult *poResult)
    1915             : {
    1916             : 
    1917             :     /*******************************************************************
    1918             :      * We first get the band metadata. So we'll can use it as metadata
    1919             :      * for all the sources.
    1920             :      *
    1921             :      * We just fetch the band metadata from 1 tile. So, we assume that:
    1922             :      * - All the bands have the same data type
    1923             :      * - All the bands have the same NODATA value
    1924             :      *
    1925             :      * It is user's responsibility to ensure the requested table fit in
    1926             :      * this schema. He/she may use the 'where' clause to ensure this
    1927             :      ******************************************************************/
    1928           0 :     int nBandsFetched = 0;
    1929           0 :     BandMetadata *poBandMetaData = GetBandsMetadata(&nBandsFetched);
    1930             : 
    1931             :     /*******************************************************************
    1932             :      * Now, we can iterate over the input query's results (metadata
    1933             :      * from all the database tiles).
    1934             :      *
    1935             :      * In this iteration, we will construct the dataset GeoTransform
    1936             :      * array and we will add each tile's band as source for each of our
    1937             :      * rasterbands.
    1938             :      ******************************************************************/
    1939           0 :     int l_nTiles = PQntuples(poResult);
    1940             : 
    1941           0 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = xmin;
    1942             : 
    1943           0 :     int nField = (GetPrimaryKeyRef() != nullptr) ? 1 : 0;
    1944             : 
    1945             :     /**
    1946             :      * Construct the dataset from metadata of all tiles,
    1947             :      * and create PostGISRasterTileDataset objects, to hold the
    1948             :      * PostGISRasterTileRasterBands objects that will be used as sources
    1949             :      **/
    1950             : 
    1951             :     int i;
    1952             : 
    1953             : #ifdef DEBUG_VERBOSE
    1954             :     CPLDebug("PostGIS_Raster",
    1955             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    1956             :              "Constructing one dataset from %d tiles",
    1957             :              l_nTiles);
    1958             : #endif
    1959             : 
    1960           0 :     papoSourcesHolders = static_cast<PostGISRasterTileDataset **>(
    1961           0 :         VSI_CALLOC_VERBOSE(l_nTiles, sizeof(PostGISRasterTileDataset *)));
    1962             : 
    1963           0 :     if (papoSourcesHolders == nullptr)
    1964             :     {
    1965           0 :         VSIFree(poBandMetaData);
    1966             : 
    1967           0 :         return false;
    1968             :     }
    1969             : 
    1970           0 :     int nValidTiles = 0;
    1971           0 :     for (i = 0; i < l_nTiles; i++)
    1972             :     {
    1973           0 :         PostGISRasterTileDataset *poRTDS = BuildRasterTileDataset(
    1974           0 :             PQgetvalue(poResult, i, nField),
    1975           0 :             (GetPrimaryKeyRef() != nullptr) ? PQgetvalue(poResult, i, 0)
    1976             :                                             : nullptr,
    1977             :             nBandsFetched, poBandMetaData);
    1978           0 :         if (poRTDS == nullptr)
    1979           0 :             continue;
    1980             : 
    1981           0 :         if (nOverviewFactor == 1 && resolutionStrategy != USER_RESOLUTION)
    1982             :         {
    1983           0 :             double tilePixelSizeX = poRTDS->adfGeoTransform[GEOTRSFRM_WE_RES];
    1984           0 :             double tilePixelSizeY = poRTDS->adfGeoTransform[GEOTRSFRM_NS_RES];
    1985             : 
    1986           0 :             if (nValidTiles == 0)
    1987             :             {
    1988           0 :                 adfGeoTransform[GEOTRSFRM_WE_RES] = tilePixelSizeX;
    1989           0 :                 adfGeoTransform[GEOTRSFRM_NS_RES] = tilePixelSizeY;
    1990             :             }
    1991             :             else
    1992             :             {
    1993           0 :                 UpdateGlobalResolutionWithTileResolution(tilePixelSizeX,
    1994             :                                                          tilePixelSizeY);
    1995             :             }
    1996             :         }
    1997             : 
    1998           0 :         papoSourcesHolders[nValidTiles++] = poRTDS;
    1999             :     }  // end for
    2000             : 
    2001           0 :     l_nTiles = nValidTiles;
    2002             : 
    2003           0 :     if (nOverviewFactor > 1)
    2004             :     {
    2005           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] =
    2006           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_WE_RES] * nOverviewFactor;
    2007           0 :         adfGeoTransform[GEOTRSFRM_NS_RES] =
    2008           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_NS_RES] * nOverviewFactor;
    2009             :     }
    2010           0 :     else if ((resolutionStrategy == AVERAGE_RESOLUTION ||
    2011           0 :               resolutionStrategy == AVERAGE_APPROX_RESOLUTION) &&
    2012             :              l_nTiles > 0)
    2013             :     {
    2014           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] /= l_nTiles;
    2015           0 :         adfGeoTransform[GEOTRSFRM_NS_RES] /= l_nTiles;
    2016             :     }
    2017             : 
    2018             :     /**
    2019             :      * Complete the rest of geotransform parameters
    2020             :      **/
    2021           0 :     if (adfGeoTransform[GEOTRSFRM_NS_RES] >= 0.0)
    2022           0 :         adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = ymin;
    2023             :     else
    2024           0 :         adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = ymax;
    2025             : 
    2026             : #ifdef DEBUG_VERBOSE
    2027             :     CPLDebug("PostGIS_Raster",
    2028             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2029             :              "GeoTransform array = (%f, %f, %f, %f, %f, %f)",
    2030             :              adfGeoTransform[GEOTRSFRM_TOPLEFT_X],
    2031             :              adfGeoTransform[GEOTRSFRM_WE_RES],
    2032             :              adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1],
    2033             :              adfGeoTransform[GEOTRSFRM_TOPLEFT_Y],
    2034             :              adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2],
    2035             :              adfGeoTransform[GEOTRSFRM_NS_RES]);
    2036             : #endif
    2037             : 
    2038             :     // Calculate the raster size from the geotransform array
    2039           0 :     nRasterXSize = static_cast<int>(
    2040           0 :         fabs(rint((xmax - xmin) / adfGeoTransform[GEOTRSFRM_WE_RES])));
    2041             : 
    2042           0 :     nRasterYSize = static_cast<int>(
    2043           0 :         fabs(rint((ymax - ymin) / adfGeoTransform[GEOTRSFRM_NS_RES])));
    2044             : 
    2045             : #ifdef DEBUG_VERBOSE
    2046             :     CPLDebug("PostGIS_Raster",
    2047             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2048             :              "Raster size: (%d, %d), ",
    2049             :              nRasterXSize, nRasterYSize);
    2050             : #endif
    2051             : 
    2052           0 :     if (nRasterXSize <= 0 || nRasterYSize <= 0)
    2053             :     {
    2054           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2055             :                     "Computed PostGIS Raster dimension is invalid. You've "
    2056             :                     "probably specified inappropriate resolution.");
    2057             : 
    2058           0 :         VSIFree(poBandMetaData);
    2059           0 :         return false;
    2060             :     }
    2061             : 
    2062             :     /*******************************************************************
    2063             :      * Now construct the dataset bands
    2064             :      ******************************************************************/
    2065           0 :     BuildBands(poBandMetaData, nBandsFetched);
    2066             : 
    2067             :     // And free bandmetadata
    2068           0 :     VSIFree(poBandMetaData);
    2069             : 
    2070             :     /*******************************************************************
    2071             :      * Finally, add complex sources and create a quadtree index for them
    2072             :      ******************************************************************/
    2073             : #ifdef DEBUG_VERBOSE
    2074             :     CPLDebug("PostGIS_Raster",
    2075             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2076             :              "Finally, adding sources for bands");
    2077             : #endif
    2078           0 :     for (int iSource = 0; iSource < l_nTiles; iSource++)
    2079             :     {
    2080           0 :         PostGISRasterTileDataset *poRTDS = papoSourcesHolders[iSource];
    2081           0 :         AddComplexSource(poRTDS);
    2082           0 :         if (poRTDS->pszPKID != nullptr)
    2083           0 :             oMapPKIDToRTDS[poRTDS->pszPKID] = poRTDS;
    2084           0 :         CPLQuadTreeInsert(hQuadTree, poRTDS);
    2085             :     }
    2086             : 
    2087           0 :     return true;
    2088             : }
    2089             : 
    2090             : /***********************************************************************
    2091             :  * \brief Construct subdatasets and show them.
    2092             :  *
    2093             :  * This method is not very elegant. It is strongly attached to
    2094             :  * SetRasterProperties (it assumes poResult is not NULL, and the actual
    2095             :  * results are stored at fixed positions). I just did it to avoid a
    2096             :  * huge SetRasterProperties method.
    2097             :  *
    2098             :  * I know, this could be avoided in a better way. Like implementing a
    2099             :  * wrapper to raise queries and get results without all the checking
    2100             :  * overhead. I'd like to do it, someday...
    2101             :  **********************************************************************/
    2102           0 : GBool PostGISRasterDataset::YieldSubdatasets(
    2103             :     PGresult *poResult, const char *pszValidConnectionString)
    2104             : {
    2105           0 :     int l_nTiles = PQntuples(poResult);
    2106           0 :     int i = 0;
    2107             : 
    2108           0 :     papszSubdatasets =
    2109           0 :         static_cast<char **>(VSICalloc(2 * l_nTiles + 1, sizeof(char *)));
    2110           0 :     if (papszSubdatasets == nullptr)
    2111           0 :         return false;
    2112             : 
    2113           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    2114             : 
    2115             :     // Subdatasets identified by primary key
    2116           0 :     if (GetPrimaryKeyRef() != nullptr)
    2117             :     {
    2118           0 :         CPLString osPrimaryKeyNameI(CPLQuotedSQLIdentifier(pszPrimaryKeyName));
    2119             : 
    2120           0 :         for (i = 0; i < l_nTiles; i++)
    2121             :         {
    2122             : 
    2123           0 :             const char *pszId = PQgetvalue(poResult, i, 0);
    2124             : 
    2125           0 :             papszSubdatasets[2 * i] = CPLStrdup(CPLSPrintf(
    2126             :                 "SUBDATASET_%d_NAME=PG:%s schema='%s' table='%s' column='%s' "
    2127             :                 "where='%s = %s'",
    2128             :                 i + 1, pszValidConnectionString, pszSchema, pszTable, pszColumn,
    2129             :                 osPrimaryKeyNameI.c_str(), pszId));
    2130             : 
    2131           0 :             papszSubdatasets[2 * i + 1] = CPLStrdup(CPLSPrintf(
    2132             :                 "SUBDATASET_%d_DESC=PostGIS Raster at %s.%s (%s), with %s = %s",
    2133             :                 i + 1, pszSchema, pszTable, pszColumn,
    2134             :                 osPrimaryKeyNameI.c_str(), pszId));
    2135             :         }
    2136             :     }
    2137             : 
    2138             :     // Subdatasets identified by upper left pixel
    2139             :     else
    2140             :     {
    2141           0 :         for (i = 0; i < l_nTiles; i++)
    2142             :         {
    2143           0 :             char *pszRes = CPLStrdup(PQgetvalue(poResult, i, 0));
    2144             : 
    2145             :             // Skip first "("
    2146           0 :             char *pszFilteredRes = pszRes + 1;
    2147             : 
    2148             :             // Skip last ")"
    2149           0 :             pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
    2150             : 
    2151             :             // Tokenize
    2152             :             char **papszParams =
    2153           0 :                 CSLTokenizeString2(pszFilteredRes, ",", CSLT_HONOURSTRINGS);
    2154             : 
    2155           0 :             CPLFree(pszRes);
    2156             : 
    2157             :             const double dfTileUpperLeftX =
    2158           0 :                 CPLAtof(papszParams[POS_UPPERLEFTX]);
    2159             :             const double dfTileUpperLeftY =
    2160           0 :                 CPLAtof(papszParams[POS_UPPERLEFTY]);
    2161             : 
    2162           0 :             papszSubdatasets[2 * i] = CPLStrdup(CPLSPrintf(
    2163             :                 "SUBDATASET_%d_NAME=PG:%s schema=%s table=%s column=%s "
    2164             :                 "where='abs(ST_UpperLeftX(%s) - %.8f) < 1e-8 AND "
    2165             :                 "abs(ST_UpperLeftY(%s) - %.8f) < 1e-8'",
    2166             :                 i + 1, pszValidConnectionString, pszSchema, pszTable, pszColumn,
    2167             :                 osColumnI.c_str(), dfTileUpperLeftX, osColumnI.c_str(),
    2168             :                 dfTileUpperLeftY));
    2169             : 
    2170           0 :             papszSubdatasets[2 * i + 1] = CPLStrdup(
    2171             :                 CPLSPrintf("SUBDATASET_%d_DESC=PostGIS Raster at %s.%s (%s), "
    2172             :                            "UpperLeft = %.8f, %.8f",
    2173             :                            i + 1, pszSchema, pszTable, pszColumn,
    2174             :                            dfTileUpperLeftX, dfTileUpperLeftY));
    2175             : 
    2176           0 :             CSLDestroy(papszParams);
    2177             :         }
    2178             :     }
    2179             : 
    2180             :     /**
    2181             :      * Not a single raster fetched. Not really needed. Just to keep code clean
    2182             :      **/
    2183           0 :     nRasterXSize = 0;
    2184           0 :     nRasterYSize = 0;
    2185           0 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = 0.0;
    2186           0 :     adfGeoTransform[GEOTRSFRM_WE_RES] = 1.0;
    2187           0 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
    2188           0 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = 0.0;
    2189           0 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
    2190           0 :     adfGeoTransform[GEOTRSFRM_NS_RES] = -1.0;
    2191             : 
    2192           0 :     return true;
    2193             : }
    2194             : 
    2195             : /***********************************************************************
    2196             :  * \brief Set the general raster properties.
    2197             :  *
    2198             :  * This method is called when the driver working mode is
    2199             :  * ONE_RASTER_PER_ROW or ONE_RASTER_PER_TABLE.
    2200             :  *
    2201             :  * We must distinguish between tiled and untiled raster coverages. In
    2202             :  * PostGIS Raster, there's no real difference between 'tile' and
    2203             :  * 'raster'. There's only 'raster objects'. Each record of a raster
    2204             :  * table is a raster object, and has its own georeference information,
    2205             :  * whether if the record is a tile of a bigger raster coverage or is a
    2206             :  * complete raster. So, <b>there's no a way of knowing if the rows of a
    2207             :  * raster table are related or not</b>. It is user's responsibility, and
    2208             :  * it is managed by 'mode' parameter in connection string, which
    2209             :  * determines the driver working mode.
    2210             :  *
    2211             :  * The user is responsible to ensure that the raster layer meets the
    2212             :  * minimum topological requirements for analysis. The ideal case is when
    2213             :  * all the raster tiles of a continuous layer are the same size, snap to
    2214             :  * the same grid and do not overlap.
    2215             :  *
    2216             :  **********************************************************************/
    2217           2 : GBool PostGISRasterDataset::SetRasterProperties(
    2218             :     const char *pszValidConnectionString)
    2219             : {
    2220           2 :     PGresult *poResult = nullptr;
    2221           2 :     GBool bDataFoundInRasterColumns = false;
    2222           2 :     GBool bNeedToCheckWholeTable = false;
    2223             : 
    2224           4 :     CPLString osCommand;
    2225           4 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    2226           4 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    2227           4 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    2228             : 
    2229             :     /*******************************************************************
    2230             :      * Get the extent and the maximum number of bands of the requested
    2231             :      * raster-
    2232             :      *
    2233             :      * TODO: The extent of rotated rasters could be a problem. We will
    2234             :      * need a ST_RotatedExtent function in PostGIS. Without that
    2235             :      * function, we should not allow rotated rasters.
    2236             :      ******************************************************************/
    2237           2 :     if (pszWhere != nullptr)
    2238             :     {
    2239             :         osCommand.Printf(
    2240             :             "select srid, nbband, ST_XMin(geom) as xmin, "
    2241             :             "ST_XMax(geom) as xmax, ST_YMin(geom) as ymin, "
    2242             :             "ST_YMax(geom) as ymax, scale_x, scale_y from (select ST_SRID(%s) "
    2243             :             "srid, "
    2244             :             "ST_Extent(%s::geometry) geom, max(ST_NumBands(%s)) "
    2245             :             "nbband, avg(ST_ScaleX(%s)) scale_x, avg(ST_ScaleY(%s)) scale_y "
    2246             :             "from %s.%s where %s group by ST_SRID(%s)) foo",
    2247             :             osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    2248             :             osColumnI.c_str(), osColumnI.c_str(), osSchemaI.c_str(),
    2249           0 :             osTableI.c_str(), pszWhere, osColumnI.c_str());
    2250             : 
    2251             : #ifdef DEBUG_QUERY
    2252             :         CPLDebug("PostGIS_Raster",
    2253             :                  "PostGISRasterDataset::SetRasterProperties(): First query: %s",
    2254             :                  osCommand.c_str());
    2255             : #endif
    2256             : 
    2257           0 :         poResult = PQexec(poConn, osCommand.c_str());
    2258             :     }
    2259             :     else
    2260             :     {
    2261             :         /**
    2262             :          * Optimization: First, check raster_columns view (it makes
    2263             :          * things faster. See ticket #5046)
    2264             :          *
    2265             :          * This can only be applied if we don't have a 'where' clause,
    2266             :          * because raster_columns view stores statistics about the whole
    2267             :          * table. If the user specified 'where' clause is because is
    2268             :          * just interested in a subset of the table rows.
    2269             :          **/
    2270             :         osCommand.Printf(
    2271             :             "select srid, nbband, ST_XMin(geom) as xmin, ST_XMax(geom) "
    2272             :             "as xmax, ST_YMin(geom) as ymin, ST_YMax(geom) as ymax, "
    2273             :             "scale_x, scale_y, blocksize_x, blocksize_y, same_alignment, "
    2274             :             "regular_blocking "
    2275             :             "from (select srid, extent geom, num_bands nbband, "
    2276             :             "scale_x, scale_y, blocksize_x, blocksize_y, same_alignment, "
    2277             :             "regular_blocking from "
    2278             :             "raster_columns where r_table_schema = '%s' and "
    2279             :             "r_table_name = '%s' and r_raster_column = '%s' ) foo",
    2280           2 :             pszSchema, pszTable, pszColumn);
    2281             : 
    2282             : #ifdef DEBUG_QUERY
    2283             :         CPLDebug("PostGIS_Raster",
    2284             :                  "PostGISRasterDataset::SetRasterProperties(): First query: %s",
    2285             :                  osCommand.c_str());
    2286             : #endif
    2287             : 
    2288           2 :         poResult = PQexec(poConn, osCommand.c_str());
    2289             : 
    2290             :         // Query execution error
    2291           4 :         if (poResult == nullptr ||
    2292           4 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2293           2 :             PQntuples(poResult) < 0)
    2294             :         {
    2295           0 :             bNeedToCheckWholeTable = true;
    2296             : 
    2297           0 :             if (poResult)
    2298           0 :                 PQclear(poResult);
    2299             :         }
    2300             : 
    2301             :         /**
    2302             :          * We didn't find anything in raster_columns view. Need to check
    2303             :          * the whole table for metadata
    2304             :          **/
    2305           2 :         else if (PQntuples(poResult) == 0)
    2306             :         {
    2307             : 
    2308           2 :             ReportError(
    2309             :                 CE_Warning, CPLE_AppDefined,
    2310             :                 "Cannot find "
    2311             :                 "information about %s.%s table in raster_columns view. The "
    2312             :                 "raster table load would take a lot of time. Please, "
    2313             :                 "execute AddRasterConstraints PostGIS function to register "
    2314             :                 "this table as raster table in raster_columns view. This "
    2315             :                 "will save a lot of time.",
    2316             :                 pszSchema, pszTable);
    2317             : 
    2318           2 :             PQclear(poResult);
    2319             : 
    2320           2 :             bNeedToCheckWholeTable = true;
    2321             :         }
    2322             : 
    2323             :         /* There's a result but the row has empty values */
    2324           0 :         else if (PQntuples(poResult) == 1 &&
    2325           0 :                  (PQgetvalue(poResult, 0, 1)[0] == '\0' ||
    2326           0 :                   (poParentDS == nullptr &&
    2327           0 :                    (PQgetvalue(poResult, 0, 2)[0] == '\0' ||
    2328           0 :                     PQgetvalue(poResult, 0, 3)[0] == '\0' ||
    2329           0 :                     PQgetvalue(poResult, 0, 4)[0] == '\0' ||
    2330           0 :                     PQgetvalue(poResult, 0, 5)[0] == '\0'))))
    2331             :         {
    2332           0 :             ReportError(
    2333             :                 CE_Warning, CPLE_AppDefined,
    2334             :                 "Cannot find (valid) "
    2335             :                 "information about %s.%s table in raster_columns view. The "
    2336             :                 "raster table load would take a lot of time. Please, "
    2337             :                 "execute AddRasterConstraints PostGIS function to register "
    2338             :                 "this table as raster table in raster_columns view. This "
    2339             :                 "will save a lot of time.",
    2340             :                 pszSchema, pszTable);
    2341             : 
    2342           0 :             PQclear(poResult);
    2343             : 
    2344           0 :             bNeedToCheckWholeTable = true;
    2345             :         }
    2346             : 
    2347             :         // We should check whole table but we can't
    2348           2 :         if (bNeedToCheckWholeTable && !bCheckAllTiles)
    2349             :         {
    2350           0 :             ReportError(
    2351             :                 CE_Failure, CPLE_AppDefined,
    2352             :                 "Cannot find "
    2353             :                 "information about %s.%s table in raster_columns view. "
    2354             :                 "Please, execute AddRasterConstraints PostGIS function to "
    2355             :                 "register this table as raster table in raster_columns "
    2356             :                 "view. This will save a lot of time. As alternative, "
    2357             :                 "provide configuration option "
    2358             :                 "PR_ALLOW_WHOLE_TABLE_SCAN=YES. With this option, the "
    2359             :                 "driver will work even without the table information "
    2360             :                 "stored in raster_columns view, but it could perform "
    2361             :                 "really slow.",
    2362             :                 pszSchema, pszTable);
    2363             : 
    2364           0 :             PQclear(poResult);
    2365             : 
    2366           0 :             return false;
    2367             :         }
    2368             : 
    2369             :         // We should check the whole table and we can
    2370           2 :         else if (bNeedToCheckWholeTable)
    2371             :         {
    2372             :             osCommand.Printf(
    2373             :                 "select srid, nbband, st_xmin(geom) as xmin, "
    2374             :                 "st_xmax(geom) as xmax, st_ymin(geom) as ymin, "
    2375             :                 "st_ymax(geom) as ymax, scale_x, scale_y from (select "
    2376             :                 "st_srid(%s) srid, "
    2377             :                 "st_extent(%s::geometry) geom, max(ST_NumBands(%s)) "
    2378             :                 "nbband, avg(ST_ScaleX(%s)) scale_x, avg(ST_ScaleY(%s)) "
    2379             :                 "scale_y from %s.%s group by st_srid(%s)) foo",
    2380             :                 osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    2381             :                 osColumnI.c_str(), osColumnI.c_str(), osSchemaI.c_str(),
    2382           2 :                 osTableI.c_str(), osColumnI.c_str());
    2383             : 
    2384             : #ifdef DEBUG_QUERY
    2385             :             CPLDebug("PostGIS_Raster",
    2386             :                      "PostGISRasterDataset::SetRasterProperties(): "
    2387             :                      "First query: %s",
    2388             :                      osCommand.c_str());
    2389             : #endif
    2390             : 
    2391           2 :             poResult = PQexec(poConn, osCommand.c_str());
    2392             :         }
    2393             : 
    2394             :         // We already found the data in raster_columns
    2395             :         else
    2396             :         {
    2397           0 :             bDataFoundInRasterColumns = true;
    2398             :         }
    2399             :     }
    2400             : 
    2401             :     // Query execution error
    2402           2 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2403           0 :         PQntuples(poResult) < 0)
    2404             :     {
    2405             : 
    2406           2 :         ReportError(CE_Failure, CPLE_AppDefined,
    2407             :                     "Error browsing database for PostGIS Raster "
    2408             :                     "properties : %s",
    2409           2 :                     PQerrorMessage(poConn));
    2410             : 
    2411           2 :         if (poResult != nullptr)
    2412           2 :             PQclear(poResult);
    2413             : 
    2414           2 :         return false;
    2415             :     }
    2416             : 
    2417           0 :     else if (PQntuples(poResult) == 0)
    2418             :     {
    2419           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2420             :                     "No results found in %s.%s. Did you specify a 'where' "
    2421             :                     "clause too restrictive?",
    2422             :                     pszSchema, pszTable);
    2423             : 
    2424           0 :         PQclear(poResult);
    2425             : 
    2426           0 :         return false;
    2427             :     }
    2428             : 
    2429             :     /**
    2430             :      * Found more than one SRID value in the table. Not allowed.
    2431             :      *
    2432             :      * TODO: We could provide an extra parameter, to transform all the
    2433             :      * tiles to the same SRID
    2434             :      **/
    2435           0 :     else if (PQntuples(poResult) > 1)
    2436             :     {
    2437           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2438             :                     "Error, the table %s.%s contains tiles with different "
    2439             :                     "srid. This feature is not yet supported by the PostGIS "
    2440             :                     "Raster driver. Please, specify a table that contains only "
    2441             :                     "tiles with the same srid or provide a 'where' constraint "
    2442             :                     "to select just the tiles with the same value for srid",
    2443             :                     pszSchema, pszTable);
    2444             : 
    2445           0 :         PQclear(poResult);
    2446             : 
    2447           0 :         return false;
    2448             :     }
    2449             : 
    2450             :     // Get some information we will probably need further
    2451           0 :     nSrid = atoi(PQgetvalue(poResult, 0, 0));
    2452           0 :     nBandsToCreate = atoi(PQgetvalue(poResult, 0, 1));
    2453           0 :     if (poParentDS != nullptr)
    2454             :     {
    2455             :         /* If we are an overview of a parent dataset, we need to adjust */
    2456             :         /* to its extent, otherwise IRasterIO() will not work properly */
    2457           0 :         xmin = poParentDS->xmin;
    2458           0 :         xmax = poParentDS->xmax;
    2459           0 :         ymin = poParentDS->ymin;
    2460           0 :         ymax = poParentDS->ymax;
    2461             :     }
    2462             :     else
    2463             :     {
    2464           0 :         xmin = CPLAtof(PQgetvalue(poResult, 0, 2));
    2465           0 :         xmax = CPLAtof(PQgetvalue(poResult, 0, 3));
    2466           0 :         ymin = CPLAtof(PQgetvalue(poResult, 0, 4));
    2467           0 :         ymax = CPLAtof(PQgetvalue(poResult, 0, 5));
    2468             :     }
    2469             : 
    2470             :     // Create the QuadTree object
    2471             :     CPLRectObj sRect;
    2472           0 :     sRect.minx = xmin;
    2473           0 :     sRect.miny = ymin;
    2474           0 :     sRect.maxx = xmax;
    2475           0 :     sRect.maxy = ymax;
    2476           0 :     hQuadTree = CPLQuadTreeCreate(&sRect, GetTileBoundingBox);
    2477             : 
    2478           0 :     double scale_x = CPLAtof(PQgetvalue(poResult, 0, 6));
    2479           0 :     double scale_y = CPLAtof(PQgetvalue(poResult, 0, 7));
    2480           0 :     if (nOverviewFactor > 1 && poParentDS != nullptr)
    2481             :     {
    2482           0 :         scale_x =
    2483           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_WE_RES] * nOverviewFactor;
    2484           0 :         scale_y =
    2485           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_NS_RES] * nOverviewFactor;
    2486             :     }
    2487           0 :     else if (resolutionStrategy == USER_RESOLUTION)
    2488             :     {
    2489           0 :         scale_x = adfGeoTransform[GEOTRSFRM_WE_RES];
    2490           0 :         scale_y = adfGeoTransform[GEOTRSFRM_NS_RES];
    2491             :     }
    2492             : 
    2493             :     // These fields can only be fetched from raster_columns view
    2494           0 :     if (bDataFoundInRasterColumns)
    2495             :     {
    2496           0 :         nTileWidth = atoi(PQgetvalue(poResult, 0, 8));
    2497           0 :         nTileHeight = atoi(PQgetvalue(poResult, 0, 9));
    2498           0 :         if (nTileWidth != 0 && nTileHeight != 0)
    2499           0 :             bTilesSameDimension = true;
    2500             : 
    2501           0 :         bAllTilesSnapToSameGrid = EQUAL(PQgetvalue(poResult, 0, 10), "t");
    2502             : 
    2503           0 :         bRegularBlocking = EQUAL(PQgetvalue(poResult, 0, 11), "t");
    2504             :     }
    2505             : 
    2506             : #ifdef DEBUG_VERBOSE
    2507             :     CPLDebug("PostGIS_Raster",
    2508             :              "PostGISRasterDataset::SetRasterProperties: xmin = %f, "
    2509             :              "xmax = %f, ymin = %f, ymax = %f, scale_x = %f, scale_y = %f",
    2510             :              xmin, xmax, ymin, ymax, scale_x, scale_y);
    2511             : #endif
    2512             : 
    2513           0 :     PQclear(poResult);
    2514             : 
    2515             :     /*******************************************************************
    2516             :      * Now, we fetch the metadata of all the raster tiles in the
    2517             :      * database, that will allow us to construct VRT sources to the
    2518             :      * PostGIS Raster bands.
    2519             :      *
    2520             :      * TODO: Improve this. If we have a big amount of tiles, it can be
    2521             :      * a problem.
    2522             :      ******************************************************************/
    2523             :     // We'll identify each tile for its primary key/unique id (ideal)
    2524           0 :     if (GetPrimaryKeyRef() != nullptr)
    2525             :     {
    2526           0 :         CPLString osPrimaryKeyNameI(CPLQuotedSQLIdentifier(pszPrimaryKeyName));
    2527             : 
    2528           0 :         if (pszWhere == nullptr)
    2529             :         {
    2530             :             /* If we don't know the pixel size, then guess it from averaging the
    2531             :              * metadata */
    2532             :             /* of a maximum 10 rasters */
    2533           0 :             if (bIsFastPK && nMode == ONE_RASTER_PER_TABLE &&
    2534           0 :                 HasSpatialIndex() && (scale_x == 0 || scale_y == 0) &&
    2535           0 :                 resolutionStrategy == AVERAGE_APPROX_RESOLUTION)
    2536             :             {
    2537             :                 osCommand.Printf("SELECT avg(scale_x) avg_scale_x, "
    2538             :                                  "avg(scale_y) avg_scale_y FROM "
    2539             :                                  "(SELECT ST_ScaleX(%s) scale_x, ST_ScaleY(%s) "
    2540             :                                  "scale_y FROM %s.%s LIMIT 10) foo",
    2541             :                                  osColumnI.c_str(), osColumnI.c_str(),
    2542           0 :                                  osSchemaI.c_str(), osTableI.c_str());
    2543             : #ifdef DEBUG_QUERY
    2544             :                 CPLDebug(
    2545             :                     "PostGIS_Raster",
    2546             :                     "PostGISRasterDataset::SetRasterProperties(): Query: %s",
    2547             :                     osCommand.c_str());
    2548             : #endif
    2549             : 
    2550           0 :                 poResult = PQexec(poConn, osCommand.c_str());
    2551           0 :                 if (poResult == nullptr ||
    2552           0 :                     PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2553           0 :                     PQntuples(poResult) <= 0)
    2554             :                 {
    2555             : 
    2556           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
    2557             :                                 "Error retrieving raster metadata");
    2558             : 
    2559           0 :                     CPLDebug("PostGIS_Raster",
    2560             :                              "PostGISRasterDataset::SetRasterProperties(): %s",
    2561           0 :                              PQerrorMessage(poConn));
    2562             : 
    2563           0 :                     if (poResult != nullptr)
    2564           0 :                         PQclear(poResult);
    2565             : 
    2566           0 :                     return false;
    2567             :                 }
    2568             : 
    2569           0 :                 scale_x = CPLAtof(PQgetvalue(poResult, 0, 0));
    2570           0 :                 scale_y = CPLAtof(PQgetvalue(poResult, 0, 1));
    2571           0 :                 CPLDebug("PostGIS_Raster",
    2572             :                          "PostGISRasterDataset::SetRasterProperties: guessed: "
    2573             :                          "scale_x = %f, scale_y = %f",
    2574             :                          scale_x, scale_y);
    2575             : 
    2576           0 :                 PQclear(poResult);
    2577             :             }
    2578             : 
    2579             :             /* If we build a raster for the whole table, than we have a spatial
    2580             :                index, and a primary key, and we know the pixel size, we can
    2581             :                build the dataset immediately, and IRasterIO() queries will be
    2582             :                able to retrieve quickly the PKID of the tiles to fetch, so we
    2583             :                don't need to scan the whole table */
    2584           0 :             if (bIsFastPK && nMode == ONE_RASTER_PER_TABLE &&
    2585           0 :                 HasSpatialIndex() && scale_x != 0 && scale_y != 0)
    2586             :             {
    2587           0 :                 adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = xmin;
    2588           0 :                 adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
    2589           0 :                 adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] =
    2590           0 :                     (scale_y < 0) ? ymax : ymin;
    2591           0 :                 adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
    2592           0 :                 adfGeoTransform[GEOTRSFRM_WE_RES] = scale_x;
    2593           0 :                 adfGeoTransform[GEOTRSFRM_NS_RES] = scale_y;
    2594             : 
    2595             :                 // Calculate the raster size from the geotransform array
    2596           0 :                 nRasterXSize = static_cast<int>(fabs(
    2597           0 :                     rint((xmax - xmin) / adfGeoTransform[GEOTRSFRM_WE_RES])));
    2598             : 
    2599           0 :                 nRasterYSize = static_cast<int>(fabs(
    2600           0 :                     rint((ymax - ymin) / adfGeoTransform[GEOTRSFRM_NS_RES])));
    2601             : 
    2602             : #ifdef DEBUG_VERBOSE
    2603             :                 CPLDebug("PostGIS_Raster",
    2604             :                          "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2605             :                          "Raster size: (%d, %d), ",
    2606             :                          nRasterXSize, nRasterYSize);
    2607             : #endif
    2608             : 
    2609           0 :                 if (nRasterXSize <= 0 || nRasterYSize <= 0)
    2610             :                 {
    2611           0 :                     ReportError(
    2612             :                         CE_Failure, CPLE_AppDefined,
    2613             :                         "Computed PostGIS Raster dimension is invalid. You "
    2614             :                         "have probably specified an inappropriate "
    2615             :                         "resolution.");
    2616             : 
    2617           0 :                     return false;
    2618             :                 }
    2619             : 
    2620           0 :                 bBuildQuadTreeDynamically = true;
    2621             : 
    2622             :                 /**************************************************************
    2623             :                  * Now construct the dataset bands
    2624             :                  ***************************************************************/
    2625           0 :                 int nBandsFetched = 0;
    2626           0 :                 BandMetadata *poBandMetaData = GetBandsMetadata(&nBandsFetched);
    2627             : 
    2628           0 :                 BuildBands(poBandMetaData, nBandsFetched);
    2629             : 
    2630             :                 // And free bandmetadata
    2631           0 :                 VSIFree(poBandMetaData);
    2632             : 
    2633           0 :                 return true;
    2634             :             }
    2635             : 
    2636             :             osCommand.Printf("select %s, st_metadata(%s) from %s.%s",
    2637             :                              osPrimaryKeyNameI.c_str(), osColumnI.c_str(),
    2638           0 :                              osSchemaI.c_str(), osTableI.c_str());
    2639             : 
    2640             :             // srid should not be necessary. It was previously checked
    2641             :         }
    2642             : 
    2643             :         else
    2644             :         {
    2645             :             osCommand.Printf("select %s, st_metadata(%s) from %s.%s "
    2646             :                              "where %s",
    2647             :                              osPrimaryKeyNameI.c_str(), osColumnI.c_str(),
    2648           0 :                              osSchemaI.c_str(), osTableI.c_str(), pszWhere);
    2649             :         }
    2650             :     }
    2651             : 
    2652             :     // No primary key/unique id found. We rely on upper left pixel
    2653             :     else
    2654             :     {
    2655           0 :         if (pszWhere == nullptr)
    2656             :         {
    2657             :             osCommand.Printf("select st_metadata(%s) from %s.%s",
    2658             :                              osColumnI.c_str(), osSchemaI.c_str(),
    2659           0 :                              osTableI.c_str());
    2660             :         }
    2661             : 
    2662             :         else
    2663             :         {
    2664             :             osCommand.Printf("select st_metadata(%s) from %s.%s where %s",
    2665             :                              osColumnI.c_str(), osSchemaI.c_str(),
    2666           0 :                              osTableI.c_str(), pszWhere);
    2667             :         }
    2668             :     }
    2669             : 
    2670             : #ifdef DEBUG_QUERY
    2671             :     CPLDebug("PostGIS_Raster",
    2672             :              "PostGISRasterDataset::SetRasterProperties(): Query: %s",
    2673             :              osCommand.c_str());
    2674             : #endif
    2675             : 
    2676           0 :     poResult = PQexec(poConn, osCommand.c_str());
    2677           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2678           0 :         PQntuples(poResult) <= 0)
    2679             :     {
    2680             : 
    2681           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2682             :                     "Error retrieving raster metadata");
    2683             : 
    2684           0 :         CPLDebug("PostGIS_Raster",
    2685             :                  "PostGISRasterDataset::SetRasterProperties(): %s",
    2686           0 :                  PQerrorMessage(poConn));
    2687             : 
    2688           0 :         if (poResult != nullptr)
    2689           0 :             PQclear(poResult);
    2690             : 
    2691           0 :         return false;
    2692             :     }
    2693             : 
    2694             :     // Now we know the number of tiles that form our dataset
    2695           0 :     m_nTiles = PQntuples(poResult);
    2696             : 
    2697             :     /*******************************************************************
    2698             :      * We are going to create a whole dataset as a mosaic with all the
    2699             :      * tiles. We'll consider each tile as a VRT source for
    2700             :      * PostGISRasterRasterBand. The data will be actually read by each
    2701             :      * of these sources, and it will be cached in the sources' caches,
    2702             :      * not in the PostGISRasterRasterBand cache
    2703             :      ******************************************************************/
    2704           0 :     if (m_nTiles == 1 || nMode == ONE_RASTER_PER_TABLE)
    2705             :     {
    2706             : #ifdef DEBUG_VERBOSE
    2707             :         CPLDebug("PostGIS_Raster",
    2708             :                  "PostGISRasterDataset::SetRasterProperties(): "
    2709             :                  "Constructing one dataset from %d tiles",
    2710             :                  m_nTiles);
    2711             : #endif
    2712             : 
    2713           0 :         GBool res = ConstructOneDatasetFromTiles(poResult);
    2714             : 
    2715           0 :         PQclear(poResult);
    2716             : 
    2717           0 :         return res;
    2718             :     }
    2719             : 
    2720             :     /***************************************************************
    2721             :      * One raster per row: collect subdatasets
    2722             :      **************************************************************/
    2723           0 :     else if (nMode == ONE_RASTER_PER_ROW)
    2724             :     {
    2725             : #ifdef DEBUG_VERBOSE
    2726             :         CPLDebug("PostGIS_Raster",
    2727             :                  "PostGISRasterDataset::SetRasterProperties(): "
    2728             :                  "Reporting %d datasets",
    2729             :                  m_nTiles);
    2730             : #endif
    2731             : 
    2732           0 :         GBool res = YieldSubdatasets(poResult, pszValidConnectionString);
    2733             : 
    2734           0 :         PQclear(poResult);
    2735             : 
    2736           0 :         return res;
    2737             :     }
    2738             : 
    2739             :     /***************************************************************
    2740             :      * Wrong mode: error
    2741             :      **************************************************************/
    2742             :     else
    2743             :     {
    2744           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2745             :                     "Wrong driver working mode. You must specify mode = 1 or "
    2746             :                     "mode = 2 in the connection string. Check PostGIS Raster "
    2747             :                     "documentation at "
    2748             :                     "http://trac.osgeo.org/gdal/wiki/frmts_wtkraster.html "
    2749             :                     "for further information about working modes.");
    2750             : 
    2751           0 :         PQclear(poResult);
    2752             : 
    2753           0 :         return false;
    2754             :     }
    2755             : }
    2756             : 
    2757             : /***********************************************************************
    2758             :  * \brief Get the connection information for a filename.
    2759             :  *
    2760             :  * This method extracts these dataset parameters from the connection
    2761             :  * string, if present:
    2762             :  * - pszSchema: The schema where the table belongs
    2763             :  * - pszTable: The table's name
    2764             :  * - pszColumn: The raster column's name
    2765             :  * - pszWhere: A where constraint to apply to the table's rows
    2766             :  * - pszHost: The PostgreSQL host
    2767             :  * - pszPort: The PostgreSQL port
    2768             :  * - pszUser: The PostgreSQL user
    2769             :  * - pszPassword: The PostgreSQL password
    2770             :  * - nMode: The connection mode
    2771             :  *
    2772             :  * If any of there parameters is not present in the connection string,
    2773             :  * default values are taken. nMode is deducted from the rest of
    2774             :  * parameters if not provided.
    2775             :  *
    2776             :  * Apart from that, bBrowseDatabase is set to TRUE if the mode is
    2777             :  * BROWSE_SCHEMA or BROWSE_DATABASE
    2778             :  **********************************************************************/
    2779             : static GBool
    2780           2 : GetConnectionInfo(const char *pszFilename, char **ppszConnectionString,
    2781             :                   char **ppszService, char **ppszDbname, char **ppszSchema,
    2782             :                   char **ppszTable, char **ppszColumn, char **ppszWhere,
    2783             :                   char **ppszHost, char **ppszPort, char **ppszUser,
    2784             :                   char **ppszPassword, WorkingMode *nMode,
    2785             :                   GBool *bBrowseDatabase, OutDBResolution *peOutDBResolution)
    2786             : {
    2787           2 :     int nPos = -1, sPos = -1, i;
    2788           2 :     char *pszTmp = nullptr;
    2789           2 :     char **papszParams = PostGISRasterParseConnectionString(pszFilename);
    2790           2 :     if (papszParams == nullptr)
    2791             :     {
    2792           0 :         return false;
    2793             :     }
    2794             : 
    2795             :     /*******************************************************************
    2796             :      * Get mode:
    2797             :      *  - 1. ONE_RASTER_PER_ROW: Each row is considered as a separate
    2798             :      *      raster
    2799             :      *  - 2. ONE_RASTER_PER_TABLE: All the table rows are considered as
    2800             :      *      a whole raster coverage
    2801             :      ******************************************************************/
    2802           2 :     nPos = CSLFindName(papszParams, "mode");
    2803           2 :     if (nPos != -1)
    2804             :     {
    2805             :         int tmp;
    2806           0 :         tmp = atoi(CPLParseNameValue(papszParams[nPos], nullptr));
    2807             : 
    2808             :         // default value
    2809           0 :         *nMode = ONE_RASTER_PER_ROW;
    2810             : 
    2811           0 :         if (tmp == 2)
    2812             :         {
    2813           0 :             *nMode = ONE_RASTER_PER_TABLE;
    2814             :         }
    2815             : 
    2816             :         /* Remove the mode from connection string */
    2817           0 :         papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2818             :     }
    2819             :     /* Default mode */
    2820             :     else
    2821           2 :         *nMode = ONE_RASTER_PER_ROW;
    2822             : 
    2823           2 :     nPos = CSLFindName(papszParams, "outdb_resolution");
    2824           2 :     *peOutDBResolution = OutDBResolution::SERVER_SIDE;
    2825           2 :     if (nPos != -1)
    2826             :     {
    2827           0 :         const char *pszValue = CPLParseNameValue(papszParams[nPos], nullptr);
    2828           0 :         if (EQUAL(pszValue, "server_side"))
    2829           0 :             *peOutDBResolution = OutDBResolution::SERVER_SIDE;
    2830           0 :         else if (EQUAL(pszValue, "client_side"))
    2831           0 :             *peOutDBResolution = OutDBResolution::CLIENT_SIDE;
    2832           0 :         else if (EQUAL(pszValue, "client_side_if_possible"))
    2833           0 :             *peOutDBResolution = OutDBResolution::CLIENT_SIDE_IF_POSSIBLE;
    2834             :         else
    2835             :         {
    2836           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2837             :                      "Unsupported value for outdb_resolution: %s", pszValue);
    2838             :         }
    2839             : 
    2840             :         /* Remove the mode from connection string */
    2841           0 :         papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2842             :     }
    2843             : 
    2844             :     /**
    2845             :      * Case 1: There's no database or service name: Error, you need, at least,
    2846             :      * specify a database or a service name (NOTE: insensitive search)
    2847             :      **/
    2848           2 :     nPos = CSLFindName(papszParams, "dbname");
    2849           2 :     sPos = CSLFindName(papszParams, "service");
    2850             : 
    2851           2 :     if (nPos == -1 && sPos == -1)
    2852             :     {
    2853           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2854             :                  "You must specify at least a db name or a service name");
    2855             : 
    2856           0 :         CSLDestroy(papszParams);
    2857             : 
    2858           0 :         return false;
    2859             :     }
    2860             : 
    2861           2 :     *ppszDbname = (nPos != -1)
    2862           2 :                       ? CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr))
    2863             :                       : nullptr;
    2864           2 :     *ppszService =
    2865           2 :         (sPos != -1) ? CPLStrdup(CPLParseNameValue(papszParams[sPos], nullptr))
    2866             :                      : nullptr;
    2867             : 
    2868             :     /**
    2869             :      * Case 2: There's a database or service name, but no table name: activate a
    2870             :      *flag for browsing the database, fetching all the schemas that contain
    2871             :      * raster tables
    2872             :      **/
    2873           2 :     nPos = CSLFindName(papszParams, "table");
    2874           2 :     if (nPos == -1)
    2875             :     {
    2876           0 :         *bBrowseDatabase = true;
    2877             : 
    2878             :         /* Get schema name, if exist */
    2879           0 :         nPos = CSLFindName(papszParams, "schema");
    2880           0 :         if (nPos != -1)
    2881             :         {
    2882           0 :             *ppszSchema =
    2883           0 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2884             : 
    2885             :             /* Delete this pair from params array */
    2886           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2887             :         }
    2888             : 
    2889             :         /**
    2890             :          * Remove the rest of the parameters, if exist (they must not be
    2891             :          * present if we want a valid PQ connection string)
    2892             :          **/
    2893           0 :         nPos = CSLFindName(papszParams, "column");
    2894           0 :         if (nPos != -1)
    2895             :         {
    2896             :             /* Delete this pair from params array */
    2897           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2898             :         }
    2899             : 
    2900           0 :         nPos = CSLFindName(papszParams, "where");
    2901           0 :         if (nPos != -1)
    2902             :         {
    2903             :             /* Delete this pair from params array */
    2904           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2905             :         }
    2906             :     }
    2907             :     else
    2908             :     {
    2909           2 :         *bBrowseDatabase = false;
    2910             : 
    2911           2 :         *ppszTable = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2912             :         /* Delete this pair from params array */
    2913           2 :         papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2914             : 
    2915             :         /**
    2916             :          * Case 3: There's database and table name, but no column
    2917             :          * name: Use a default column name and use the table to create
    2918             :          * the dataset
    2919             :          **/
    2920           2 :         nPos = CSLFindName(papszParams, "column");
    2921           2 :         if (nPos == -1)
    2922             :         {
    2923           2 :             *ppszColumn = CPLStrdup(DEFAULT_COLUMN);
    2924             :         }
    2925             :         /**
    2926             :          * Case 4: There's database, table and column name: Use the
    2927             :          * table to create a dataset
    2928             :          **/
    2929             :         else
    2930             :         {
    2931           0 :             *ppszColumn =
    2932           0 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2933             : 
    2934             :             /* Delete this pair from params array */
    2935           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2936             :         }
    2937             : 
    2938             :         /* Get the rest of the parameters, if exist */
    2939           2 :         nPos = CSLFindName(papszParams, "schema");
    2940           2 :         if (nPos == -1)
    2941             :         {
    2942           0 :             *ppszSchema = CPLStrdup(DEFAULT_SCHEMA);
    2943             :         }
    2944             :         else
    2945             :         {
    2946           2 :             *ppszSchema =
    2947           2 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2948             : 
    2949             :             /* Delete this pair from params array */
    2950           2 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2951             :         }
    2952             : 
    2953           2 :         nPos = CSLFindName(papszParams, "where");
    2954           2 :         if (nPos != -1)
    2955             :         {
    2956           0 :             *ppszWhere =
    2957           0 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2958             : 
    2959             :             /* Delete this pair from params array */
    2960           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2961             :         }
    2962             :     }
    2963             : 
    2964             :     /* Parse ppszWhere, if needed */
    2965           2 :     if (*ppszWhere)
    2966             :     {
    2967             :         pszTmp =
    2968           0 :             ReplaceQuotes(*ppszWhere, static_cast<int>(strlen(*ppszWhere)));
    2969           0 :         CPLFree(*ppszWhere);
    2970           0 :         *ppszWhere = pszTmp;
    2971             :     }
    2972             : 
    2973             :     /***************************************
    2974             :      * Construct a valid connection string
    2975             :      ***************************************/
    2976           2 :     CPLString osConnectionString;
    2977           4 :     for (i = 0; i < CSLCount(papszParams); i++)
    2978             :     {
    2979           2 :         osConnectionString += papszParams[i];
    2980           2 :         osConnectionString += " ";
    2981             :     }
    2982             : 
    2983             :     /**********************************************************
    2984             :      * Set application name if not found in connection string
    2985             :      **********************************************************/
    2986             : 
    2987           2 :     if (*bBrowseDatabase == FALSE && *nMode == ONE_RASTER_PER_TABLE &&
    2988           4 :         CSLFindName(papszParams, "application_name") == -1 &&
    2989           0 :         getenv("PGAPPNAME") == nullptr)
    2990             :     {
    2991           0 :         osConnectionString += "application_name=";
    2992           0 :         osConnectionString += "'";
    2993           0 :         osConnectionString += "GDAL ";
    2994           0 :         osConnectionString += GDALVersionInfo("RELEASE_NAME");
    2995           0 :         osConnectionString += "'";
    2996           0 :         osConnectionString += " ";
    2997             :     }
    2998             : 
    2999           2 :     *ppszConnectionString = CPLStrdup(osConnectionString);
    3000             : 
    3001           2 :     nPos = CSLFindName(papszParams, "host");
    3002           2 :     if (nPos != -1)
    3003             :     {
    3004           0 :         *ppszHost = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3005             :     }
    3006           2 :     else if (CPLGetConfigOption("PGHOST", nullptr) != nullptr)
    3007             :     {
    3008           2 :         *ppszHost = CPLStrdup(CPLGetConfigOption("PGHOST", nullptr));
    3009             :     }
    3010             :     else
    3011           0 :         *ppszHost = nullptr;
    3012             :     /*else {
    3013             :         CPLError(CE_Failure, CPLE_AppDefined,
    3014             :             "Host parameter must be provided, or PGHOST environment "
    3015             :             "variable must be set. Please set the host and try again.");
    3016             : 
    3017             :         CSLDestroy(papszParams);
    3018             : 
    3019             :         return false;
    3020             :     }*/
    3021             : 
    3022           2 :     nPos = CSLFindName(papszParams, "port");
    3023           2 :     if (nPos != -1)
    3024             :     {
    3025           0 :         *ppszPort = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3026             :     }
    3027           2 :     else if (CPLGetConfigOption("PGPORT", nullptr) != nullptr)
    3028             :     {
    3029           2 :         *ppszPort = CPLStrdup(CPLGetConfigOption("PGPORT", nullptr));
    3030             :     }
    3031             :     else
    3032           0 :         *ppszPort = nullptr;
    3033             :     /*else {
    3034             :         CPLError(CE_Failure, CPLE_AppDefined,
    3035             :             "Port parameter must be provided, or PGPORT environment "
    3036             :             "variable must be set. Please set the port and try again.");
    3037             : 
    3038             :         CSLDestroy(papszParams);
    3039             : 
    3040             :         return false;
    3041             :     }*/
    3042             : 
    3043           2 :     nPos = CSLFindName(papszParams, "user");
    3044           2 :     if (nPos != -1)
    3045             :     {
    3046           0 :         *ppszUser = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3047             :     }
    3048           2 :     else if (CPLGetConfigOption("PGUSER", nullptr) != nullptr)
    3049             :     {
    3050           2 :         *ppszUser = CPLStrdup(CPLGetConfigOption("PGUSER", nullptr));
    3051             :     }
    3052             :     else
    3053           0 :         *ppszUser = nullptr;
    3054             :     /*else {
    3055             :         CPLError(CE_Failure, CPLE_AppDefined,
    3056             :             "User parameter must be provided, or PGUSER environment "
    3057             :             "variable must be set. Please set the user and try again.");
    3058             : 
    3059             :         CSLDestroy(papszParams);
    3060             : 
    3061             :         return false;
    3062             :     }*/
    3063             : 
    3064           2 :     nPos = CSLFindName(papszParams, "password");
    3065           2 :     if (nPos != -1)
    3066             :     {
    3067           0 :         *ppszPassword =
    3068           0 :             CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3069             :     }
    3070           2 :     else if (CPLGetConfigOption("PGPASSWORD", nullptr) != nullptr)
    3071             :     {
    3072           2 :         *ppszPassword = CPLStrdup(CPLGetConfigOption("PGPASSWORD", nullptr));
    3073             :     }
    3074             :     else
    3075           0 :         *ppszPassword = nullptr;
    3076             : 
    3077           2 :     CSLDestroy(papszParams);
    3078             : 
    3079             : #ifdef DEBUG_VERBOSE
    3080             :     CPLDebug("PostGIS_Raster",
    3081             :              "PostGISRasterDataset::GetConnectionInfo(): "
    3082             :              "Mode: %d\n"
    3083             :              "Service :%s\n"
    3084             :              "Dbname: %s\n"
    3085             :              "Schema: %s\n"
    3086             :              "Table: %s\n"
    3087             :              "Column: %s\n"
    3088             :              "Where: %s\n"
    3089             :              "Host: %s\n"
    3090             :              "Port: %s\n"
    3091             :              "User: %s\n"
    3092             :              "Password: %s\n"
    3093             :              "Connection String: %s\n",
    3094             :              *nMode, *ppszService ? *ppszService : "(null)",
    3095             :              *ppszDbname ? *ppszDbname : "(null)",
    3096             :              *ppszSchema ? *ppszSchema : "(null)",
    3097             :              *ppszTable ? *ppszTable : "(null)",
    3098             :              *ppszColumn ? *ppszColumn : "(null)",
    3099             :              *ppszWhere ? *ppszWhere : "(null)",
    3100             :              *ppszHost ? *ppszHost : "(null)", *ppszPort ? *ppszPort : "(null)",
    3101             :              *ppszUser ? *ppszUser : "(null)",
    3102             :              *ppszPassword ? *ppszPassword : "(null)", *ppszConnectionString);
    3103             : #endif
    3104             : 
    3105           2 :     return true;
    3106             : }
    3107             : 
    3108             : /***********************************************************************
    3109             :  * \brief Create a connection to a postgres database
    3110             :  **********************************************************************/
    3111           2 : static PGconn *GetConnection(const char *pszFilename,
    3112             :                              char **ppszConnectionString, char **ppszSchema,
    3113             :                              char **ppszTable, char **ppszColumn,
    3114             :                              char **ppszWhere, WorkingMode *nMode,
    3115             :                              GBool *bBrowseDatabase,
    3116             :                              OutDBResolution *peOutDBResolution)
    3117             : {
    3118           2 :     PGconn *poConn = nullptr;
    3119           2 :     char *pszService = nullptr;
    3120           2 :     char *pszDbname = nullptr;
    3121           2 :     char *pszHost = nullptr;
    3122           2 :     char *pszPort = nullptr;
    3123           2 :     char *pszUser = nullptr;
    3124           2 :     char *pszPassword = nullptr;
    3125             : 
    3126           2 :     if (GetConnectionInfo(pszFilename, ppszConnectionString, &pszService,
    3127             :                           &pszDbname, ppszSchema, ppszTable, ppszColumn,
    3128             :                           ppszWhere, &pszHost, &pszPort, &pszUser, &pszPassword,
    3129           2 :                           nMode, bBrowseDatabase, peOutDBResolution))
    3130             :     {
    3131             :         /**************************************************************
    3132             :          * Open a new database connection
    3133             :          **************************************************************/
    3134           2 :         poConn = PostGISRasterDriver::gpoPostGISRasterDriver->GetConnection(
    3135             :             *ppszConnectionString, pszService, pszDbname, pszHost, pszPort,
    3136             :             pszUser);
    3137             : 
    3138           2 :         if (poConn == nullptr)
    3139             :         {
    3140           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3141             :                      "Couldn't establish a database connection");
    3142             :         }
    3143             :     }
    3144             : 
    3145           2 :     CPLFree(pszService);
    3146           2 :     CPLFree(pszDbname);
    3147           2 :     CPLFree(pszHost);
    3148           2 :     CPLFree(pszPort);
    3149           2 :     CPLFree(pszUser);
    3150           2 :     CPLFree(pszPassword);
    3151             : 
    3152           2 :     return poConn;
    3153             : }
    3154             : 
    3155             : /***********************************************************************
    3156             :  * \brief Open a connection with PostgreSQL. The connection string will
    3157             :  * have the PostgreSQL accepted format, plus the next key=value pairs:
    3158             :  *  schema = &lt;schema_name&gt;
    3159             :  *  table = &lt;table_name&gt;
    3160             :  *  column = &lt;column_name&gt;
    3161             :  *  where = &lt;SQL where&gt;
    3162             :  *  mode = &lt;working mode&gt; (1 or 2)
    3163             :  *
    3164             :  * These pairs are used for selecting the right raster table.
    3165             :  **********************************************************************/
    3166           2 : GDALDataset *PostGISRasterDataset::Open(GDALOpenInfo *poOpenInfo)
    3167             : {
    3168           2 :     char *pszConnectionString = nullptr;
    3169           2 :     char *pszSchema = nullptr;
    3170           2 :     char *pszTable = nullptr;
    3171           2 :     char *pszColumn = nullptr;
    3172           2 :     char *pszWhere = nullptr;
    3173           2 :     WorkingMode nMode = NO_MODE;
    3174           2 :     PGconn *poConn = nullptr;
    3175           2 :     PostGISRasterDataset *poDS = nullptr;
    3176           2 :     GBool bBrowseDatabase = false;
    3177             :     OutDBResolution eOutDBResolution;
    3178             : 
    3179             :     /**************************
    3180             :      * Check input parameter
    3181             :      **************************/
    3182           2 :     if (!PostGISRasterDriverIdentify(poOpenInfo))
    3183           0 :         return nullptr;
    3184             : 
    3185           2 :     poConn = GetConnection(poOpenInfo->pszFilename, &pszConnectionString,
    3186             :                            &pszSchema, &pszTable, &pszColumn, &pszWhere, &nMode,
    3187             :                            &bBrowseDatabase, &eOutDBResolution);
    3188           2 :     if (poConn == nullptr)
    3189             :     {
    3190           0 :         CPLFree(pszConnectionString);
    3191           0 :         CPLFree(pszSchema);
    3192           0 :         CPLFree(pszTable);
    3193           0 :         CPLFree(pszColumn);
    3194           0 :         CPLFree(pszWhere);
    3195           0 :         return nullptr;
    3196             :     }
    3197             : 
    3198             :     /* For CLIENT_SIDE_IF_POSSIBLE mode, check if PostGIS 2.5 / ST_BandFileSize
    3199             :      */
    3200             :     /* is available */
    3201           2 :     bool bHasStBandFileSize = false;
    3202           2 :     if (eOutDBResolution == OutDBResolution::CLIENT_SIDE_IF_POSSIBLE)
    3203             :     {
    3204             :         const CPLString osCommand(
    3205           0 :             "SELECT 1 FROM pg_proc WHERE proname = 'st_bandfilesize'");
    3206             : #ifdef DEBUG_QUERY
    3207             :         CPLDebug("PostGIS_Raster", "PostGISRasterDataset::Open(): Query: %s",
    3208             :                  osCommand.c_str());
    3209             : #endif
    3210             : 
    3211           0 :         PGresult *poResult = PQexec(poConn, osCommand);
    3212           0 :         if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
    3213           0 :             PQntuples(poResult) == 1)
    3214             :         {
    3215             : #ifdef DEBUG_VERBOSE
    3216             :             CPLDebug("PostGIS_Raster", "ST_BandFileSize available");
    3217             : #endif
    3218           0 :             bHasStBandFileSize = true;
    3219             :         }
    3220           0 :         else if (poResult && PQresultStatus(poResult) != PGRES_TUPLES_OK)
    3221             :         {
    3222           0 :             CPLDebug("PostGIS_Raster", "PostGISRasterDataset::Open(): %s",
    3223             :                      PQerrorMessage(poConn));
    3224             :         }
    3225             : 
    3226           0 :         if (poResult)
    3227           0 :             PQclear(poResult);
    3228             :     }
    3229             : 
    3230             :     /*******************************************************************
    3231             :      * No table will be read. Only shows information about the existent
    3232             :      * raster tables
    3233             :      ******************************************************************/
    3234           2 :     if (bBrowseDatabase)
    3235             :     {
    3236             :         /**
    3237             :          * Creates empty dataset object, only for subdatasets
    3238             :          **/
    3239           0 :         poDS = new PostGISRasterDataset();
    3240           0 :         poDS->poConn = poConn;
    3241           0 :         poDS->eAccess = GA_ReadOnly;
    3242             :         // poDS->poDriver = poDriver;
    3243           0 :         poDS->nMode = (pszSchema) ? BROWSE_SCHEMA : BROWSE_DATABASE;
    3244           0 :         poDS->eOutDBResolution = eOutDBResolution;
    3245           0 :         poDS->bHasStBandFileSize = bHasStBandFileSize;
    3246             : 
    3247             :         /**
    3248             :          * Look for raster tables at database and
    3249             :          * store them as subdatasets
    3250             :          **/
    3251           0 :         if (!poDS->BrowseDatabase(pszSchema, pszConnectionString))
    3252             :         {
    3253           0 :             CPLFree(pszConnectionString);
    3254           0 :             delete poDS;
    3255             : 
    3256           0 :             if (pszSchema)
    3257           0 :                 CPLFree(pszSchema);
    3258           0 :             if (pszTable)
    3259           0 :                 CPLFree(pszTable);
    3260           0 :             if (pszColumn)
    3261           0 :                 CPLFree(pszColumn);
    3262           0 :             if (pszWhere)
    3263           0 :                 CPLFree(pszWhere);
    3264             : 
    3265           0 :             return nullptr;
    3266             :         }
    3267             : 
    3268           0 :         if (pszSchema)
    3269           0 :             CPLFree(pszSchema);
    3270           0 :         if (pszTable)
    3271           0 :             CPLFree(pszTable);
    3272           0 :         if (pszColumn)
    3273           0 :             CPLFree(pszColumn);
    3274           0 :         if (pszWhere)
    3275           0 :             CPLFree(pszWhere);
    3276             :     }
    3277             : 
    3278             :     /*******************************************************************
    3279             :      * A table will be read as dataset: Fetch raster properties from db.
    3280             :      ******************************************************************/
    3281             :     else
    3282             :     {
    3283           2 :         poDS = new PostGISRasterDataset();
    3284           2 :         poDS->poConn = poConn;
    3285           2 :         poDS->eAccess = poOpenInfo->eAccess;
    3286           2 :         poDS->nMode = nMode;
    3287           2 :         poDS->eOutDBResolution = eOutDBResolution;
    3288           2 :         poDS->bHasStBandFileSize = bHasStBandFileSize;
    3289             :         // poDS->poDriver = poDriver;
    3290             : 
    3291           2 :         poDS->pszSchema = pszSchema;
    3292           2 :         poDS->pszTable = pszTable;
    3293           2 :         poDS->pszColumn = pszColumn;
    3294           2 :         poDS->pszWhere = pszWhere;
    3295             : 
    3296             :         /**
    3297             :          * Fetch basic raster metadata from db
    3298             :          **/
    3299             : #ifdef DEBUG_VERBOSE
    3300             :         CPLDebug("PostGIS_Raster", "Open:: connection string = %s",
    3301             :                  pszConnectionString);
    3302             : #endif
    3303             : 
    3304           2 :         if (!poDS->SetRasterProperties(pszConnectionString))
    3305             :         {
    3306           2 :             CPLFree(pszConnectionString);
    3307           2 :             delete poDS;
    3308           2 :             return nullptr;
    3309             :         }
    3310             :     }
    3311             : 
    3312           0 :     CPLFree(pszConnectionString);
    3313           0 :     return poDS;
    3314             : }
    3315             : 
    3316             : /************************************************************************/
    3317             : /*                      GetMetadataDomainList()                         */
    3318             : /************************************************************************/
    3319             : 
    3320           0 : char **PostGISRasterDataset::GetMetadataDomainList()
    3321             : {
    3322           0 :     return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
    3323           0 :                                    "SUBDATASETS", nullptr);
    3324             : }
    3325             : 
    3326             : /*****************************************
    3327             :  * \brief Get Metadata from raster
    3328             :  * TODO: Add more options (the result of
    3329             :  * calling ST_Metadata, for example)
    3330             :  *****************************************/
    3331           0 : char **PostGISRasterDataset::GetMetadata(const char *pszDomain)
    3332             : {
    3333           0 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    3334           0 :         return papszSubdatasets;
    3335             :     else
    3336           0 :         return GDALDataset::GetMetadata(pszDomain);
    3337             : }
    3338             : 
    3339             : /*****************************************************
    3340             :  * \brief Fetch the projection definition string
    3341             :  * for this dataset in OpenGIS WKT format. It should
    3342             :  * be suitable for use with the OGRSpatialReference
    3343             :  * class.
    3344             :  *****************************************************/
    3345           0 : const OGRSpatialReference *PostGISRasterDataset::GetSpatialRef() const
    3346             : {
    3347             : 
    3348           0 :     if (nSrid == -1)
    3349           0 :         return nullptr;
    3350             : 
    3351           0 :     if (!m_oSRS.IsEmpty())
    3352           0 :         return &m_oSRS;
    3353             : 
    3354             :     /********************************************************
    3355             :      *          Reading proj from database
    3356             :      ********************************************************/
    3357           0 :     CPLString osCommand;
    3358           0 :     osCommand.Printf("SELECT srtext FROM spatial_ref_sys where SRID=%d", nSrid);
    3359           0 :     PGresult *poResult = PQexec(this->poConn, osCommand.c_str());
    3360           0 :     if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
    3361           0 :         PQntuples(poResult) > 0)
    3362             :     {
    3363           0 :         const char *pszProjection = PQgetvalue(poResult, 0, 0);
    3364           0 :         if (pszProjection && pszProjection[0])
    3365           0 :             m_oSRS.importFromWkt(pszProjection);
    3366             :     }
    3367             : 
    3368           0 :     if (poResult)
    3369           0 :         PQclear(poResult);
    3370             : 
    3371           0 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3372             : }
    3373             : 
    3374             : /**********************************************************
    3375             :  * \brief Set projection definition. The input string must
    3376             :  * be in OGC WKT or PROJ.4 format
    3377             :  **********************************************************/
    3378           0 : CPLErr PostGISRasterDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3379             : {
    3380             : 
    3381           0 :     if (poSRS == nullptr)
    3382           0 :         return CE_None;
    3383             : 
    3384           0 :     CPLString osCommand;
    3385             : 
    3386             :     /*****************************************************************
    3387             :      * Check if the dataset allows updating
    3388             :      *****************************************************************/
    3389           0 :     if (GetAccess() != GA_Update)
    3390             :     {
    3391           0 :         ReportError(CE_Failure, CPLE_NoWriteAccess,
    3392             :                     "This driver doesn't allow write access");
    3393           0 :         return CE_Failure;
    3394             :     }
    3395             : 
    3396             :     /*****************************************************************
    3397             :      * Look for projection with this text
    3398             :      *****************************************************************/
    3399             : 
    3400           0 :     char *pszWKT = nullptr;
    3401           0 :     poSRS->exportToWkt(&pszWKT);
    3402           0 :     if (pszWKT == nullptr)
    3403           0 :         return CE_Failure;
    3404             : 
    3405           0 :     osCommand.Printf("SELECT srid FROM spatial_ref_sys where srtext='%s'",
    3406           0 :                      pszWKT);
    3407           0 :     CPLFree(pszWKT);
    3408           0 :     PGresult *poResult = PQexec(poConn, osCommand.c_str());
    3409             : 
    3410           0 :     if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
    3411           0 :         PQntuples(poResult) > 0)
    3412             :     {
    3413             : 
    3414           0 :         const int nFetchedSrid = atoi(PQgetvalue(poResult, 0, 0));
    3415             : 
    3416             :         // update class attribute
    3417           0 :         nSrid = nFetchedSrid;
    3418             : 
    3419             :         // update raster_columns table
    3420             :         osCommand.Printf("UPDATE raster_columns SET srid=%d WHERE \
    3421             :                     r_table_name = '%s' AND r_column = '%s'",
    3422           0 :                          nSrid, pszTable, pszColumn);
    3423           0 :         poResult = PQexec(poConn, osCommand.c_str());
    3424           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3425             :         {
    3426           0 :             ReportError(CE_Failure, CPLE_AppDefined,
    3427             :                         "Couldn't update raster_columns table: %s",
    3428           0 :                         PQerrorMessage(poConn));
    3429           0 :             return CE_Failure;
    3430             :         }
    3431             : 
    3432             :         // TODO: Update ALL blocks with the new srid...
    3433             : 
    3434           0 :         return CE_None;
    3435             :     }
    3436             :     else
    3437             :     {
    3438           0 :         ReportError(CE_Failure, CPLE_WrongFormat,
    3439             :                     "Couldn't find WKT definition");
    3440           0 :         return CE_Failure;
    3441             :     }
    3442             : }
    3443             : 
    3444             : /********************************************************
    3445             :  * \brief Set the affine transformation coefficients
    3446             :  ********************************************************/
    3447           0 : CPLErr PostGISRasterDataset::SetGeoTransform(double *padfGeoTransform)
    3448             : {
    3449           0 :     if (!padfGeoTransform)
    3450           0 :         return CE_Failure;
    3451             : 
    3452           0 :     memcpy(adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
    3453             : 
    3454           0 :     return CE_None;
    3455             : }
    3456             : 
    3457             : /********************************************************
    3458             :  * \brief Get the affine transformation coefficients
    3459             :  ********************************************************/
    3460           0 : CPLErr PostGISRasterDataset::GetGeoTransform(double *padfGeoTransform)
    3461             : {
    3462             : 
    3463             :     // copy necessary values in supplied buffer
    3464           0 :     memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
    3465             : 
    3466           0 :     if (nRasterXSize == 0 && nRasterYSize == 0)
    3467           0 :         return CE_Failure;
    3468             : 
    3469             :     /* To avoid QGIS trying to create a warped VRT for what is really */
    3470             :     /* an ungeoreferenced dataset */
    3471           0 :     if (CPLIsEqual(padfGeoTransform[0], 0.0) &&
    3472           0 :         CPLIsEqual(padfGeoTransform[1], 1.0) &&
    3473           0 :         CPLIsEqual(padfGeoTransform[2], 0.0) &&
    3474           0 :         CPLIsEqual(padfGeoTransform[3], 0.0) &&
    3475           0 :         CPLIsEqual(padfGeoTransform[4], 0.0) &&
    3476           0 :         CPLIsEqual(padfGeoTransform[5], 1.0))
    3477             :     {
    3478           0 :         return CE_Failure;
    3479             :     }
    3480             : 
    3481           0 :     return CE_None;
    3482             : }
    3483             : 
    3484             : /*********************************************************
    3485             :  * \brief Fetch files forming dataset.
    3486             :  *
    3487             :  * We need to define this method because the VRTDataset
    3488             :  * method doesn't check for NULL FileList before trying
    3489             :  * to collect the names of all sources' file list.
    3490             :  *********************************************************/
    3491           0 : char **PostGISRasterDataset::GetFileList()
    3492             : {
    3493           0 :     return nullptr;
    3494             : }
    3495             : 
    3496             : /********************************************************
    3497             :  * \brief Create a copy of a PostGIS Raster dataset.
    3498             :  ********************************************************/
    3499          18 : GDALDataset *PostGISRasterDataset::CreateCopy(
    3500             :     CPL_UNUSED const char *pszFilename, GDALDataset *poGSrcDS,
    3501             :     CPL_UNUSED int bStrict, CPL_UNUSED char **papszOptions,
    3502             :     CPL_UNUSED GDALProgressFunc pfnProgress, CPL_UNUSED void *pProgressData)
    3503             : {
    3504          18 :     char *pszSchema = nullptr;
    3505          18 :     char *pszTable = nullptr;
    3506          18 :     char *pszColumn = nullptr;
    3507          18 :     char *pszWhere = nullptr;
    3508          18 :     GBool bBrowseDatabase = false;
    3509             :     WorkingMode nMode;
    3510             :     OutDBResolution eOutDBResolution;
    3511          18 :     char *pszConnectionString = nullptr;
    3512          18 :     PGconn *poConn = nullptr;
    3513          18 :     PGresult *poResult = nullptr;
    3514             :     GBool bInsertSuccess;
    3515             : 
    3516          36 :     CPLString osCommand;
    3517             : 
    3518          18 :     if (poGSrcDS->GetDriver() != GDALGetDriverByName("PostGISRaster"))
    3519             :     {
    3520          18 :         CPLError(CE_Failure, CPLE_NotSupported,
    3521             :                  "PostGISRasterDataset::CreateCopy() only works on source "
    3522             :                  "datasets that are PostGISRaster");
    3523          18 :         return nullptr;
    3524             :     }
    3525             : 
    3526             :     // Now we can do the cast
    3527             :     PostGISRasterDataset *poSrcDS =
    3528           0 :         cpl::down_cast<PostGISRasterDataset *>(poGSrcDS);
    3529             : 
    3530             :     // Check connection string
    3531           0 :     if (pszFilename == nullptr || !STARTS_WITH_CI(pszFilename, "PG:"))
    3532             :     {
    3533             :         /**
    3534             :          * The connection string provided is not a valid connection
    3535             :          * string.
    3536             :          */
    3537           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3538             :                  "PostGIS Raster driver was unable to parse the provided "
    3539             :                  "connection string.");
    3540           0 :         return nullptr;
    3541             :     }
    3542             : 
    3543           0 :     poConn = GetConnection(pszFilename, &pszConnectionString, &pszSchema,
    3544             :                            &pszTable, &pszColumn, &pszWhere, &nMode,
    3545             :                            &bBrowseDatabase, &eOutDBResolution);
    3546           0 :     if (poConn == nullptr || bBrowseDatabase || pszTable == nullptr)
    3547             :     {
    3548           0 :         CPLFree(pszConnectionString);
    3549           0 :         CPLFree(pszSchema);
    3550           0 :         CPLFree(pszTable);
    3551           0 :         CPLFree(pszColumn);
    3552           0 :         CPLFree(pszWhere);
    3553             : 
    3554             :         // if connection info fails, browsing mode, or no table set
    3555           0 :         return nullptr;
    3556             :     }
    3557             : 
    3558           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    3559           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    3560           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    3561             : 
    3562             :     // begin transaction
    3563           0 :     poResult = PQexec(poConn, "begin");
    3564           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3565             :     {
    3566           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3567             :                  "Error beginning database transaction: %s",
    3568             :                  PQerrorMessage(poConn));
    3569           0 :         if (poResult != nullptr)
    3570           0 :             PQclear(poResult);
    3571           0 :         CPLFree(pszSchema);
    3572           0 :         CPLFree(pszTable);
    3573           0 :         CPLFree(pszColumn);
    3574           0 :         CPLFree(pszWhere);
    3575             : 
    3576           0 :         CPLFree(pszConnectionString);
    3577             : 
    3578           0 :         return nullptr;
    3579             :     }
    3580             : 
    3581           0 :     PQclear(poResult);
    3582             : 
    3583             :     // create table for raster (if not exists because a
    3584             :     // dataset will not be reported for an empty table)
    3585             : 
    3586             :     // TODO: is 'rid' necessary?
    3587           0 :     osCommand.Printf("create table if not exists %s.%s (rid serial, %s "
    3588             :                      "raster, constraint %s_pkey primary key (rid));",
    3589           0 :                      pszSchema, pszTable, pszColumn, pszTable);
    3590           0 :     poResult = PQexec(poConn, osCommand.c_str());
    3591           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3592             :     {
    3593             : 
    3594           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3595             :                  "Error creating needed tables: %s", PQerrorMessage(poConn));
    3596           0 :         if (poResult != nullptr)
    3597           0 :             PQclear(poResult);
    3598             : 
    3599             :         // rollback
    3600           0 :         poResult = PQexec(poConn, "rollback");
    3601           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3602             :         {
    3603             : 
    3604           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3605             :                      "Error rolling back transaction: %s",
    3606             :                      PQerrorMessage(poConn));
    3607             :         }
    3608           0 :         if (poResult != nullptr)
    3609           0 :             PQclear(poResult);
    3610           0 :         if (pszSchema)
    3611           0 :             CPLFree(pszSchema);
    3612           0 :         if (pszTable)
    3613           0 :             CPLFree(pszTable);
    3614           0 :         if (pszColumn)
    3615           0 :             CPLFree(pszColumn);
    3616           0 :         if (pszWhere)
    3617           0 :             CPLFree(pszWhere);
    3618             : 
    3619           0 :         CPLFree(pszConnectionString);
    3620             : 
    3621           0 :         return nullptr;
    3622             :     }
    3623             : 
    3624           0 :     PQclear(poResult);
    3625             : 
    3626           0 :     CPLString osIdxNameI;
    3627           0 :     osIdxNameI.Printf("%s_%s_gist", pszTable, pszColumn);
    3628           0 :     osIdxNameI = CPLQuotedSQLIdentifier(osIdxNameI);
    3629             : 
    3630             :     osCommand.Printf("create index %s ON %s.%s USING gist "
    3631             :                      "(st_convexhull(%s));",
    3632             :                      osIdxNameI.c_str(), osSchemaI.c_str(), osTableI.c_str(),
    3633           0 :                      osColumnI.c_str());
    3634           0 :     poResult = PQexec(poConn, osCommand.c_str());
    3635           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3636             :     {
    3637             : 
    3638           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error creating needed index: %s",
    3639             :                  PQerrorMessage(poConn));
    3640           0 :         if (poResult != nullptr)
    3641           0 :             PQclear(poResult);
    3642             : 
    3643             :         // rollback
    3644           0 :         poResult = PQexec(poConn, "rollback");
    3645           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3646             :         {
    3647             : 
    3648           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3649             :                      "Error rolling back transaction: %s",
    3650             :                      PQerrorMessage(poConn));
    3651             :         }
    3652           0 :         if (poResult != nullptr)
    3653           0 :             PQclear(poResult);
    3654           0 :         if (pszSchema)
    3655           0 :             CPLFree(pszSchema);
    3656           0 :         if (pszTable)
    3657           0 :             CPLFree(pszTable);
    3658           0 :         if (pszColumn)
    3659           0 :             CPLFree(pszColumn);
    3660           0 :         if (pszWhere)
    3661           0 :             CPLFree(pszWhere);
    3662             : 
    3663           0 :         CPLFree(pszConnectionString);
    3664             : 
    3665           0 :         return nullptr;
    3666             :     }
    3667             : 
    3668           0 :     PQclear(poResult);
    3669             : 
    3670           0 :     const char *pszSubdatasetName = nullptr;
    3671           0 :     PostGISRasterDataset *poSubDS = nullptr;
    3672           0 :     if (poSrcDS->nMode == ONE_RASTER_PER_TABLE)
    3673             :     {
    3674             :         // one raster per table
    3675             : 
    3676             :         // insert one raster
    3677             :         bInsertSuccess =
    3678           0 :             InsertRaster(poConn, poSrcDS, pszSchema, pszTable, pszColumn);
    3679           0 :         if (!bInsertSuccess)
    3680             :         {
    3681             :             // rollback
    3682           0 :             poResult = PQexec(poConn, "rollback");
    3683           0 :             if (poResult == nullptr ||
    3684           0 :                 PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3685             :             {
    3686             : 
    3687           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3688             :                          "Error rolling back transaction: %s",
    3689             :                          PQerrorMessage(poConn));
    3690             :             }
    3691           0 :             if (poResult != nullptr)
    3692           0 :                 PQclear(poResult);
    3693           0 :             if (pszSchema)
    3694           0 :                 CPLFree(pszSchema);
    3695           0 :             if (pszTable)
    3696           0 :                 CPLFree(pszTable);
    3697           0 :             if (pszColumn)
    3698           0 :                 CPLFree(pszColumn);
    3699           0 :             if (pszWhere)
    3700           0 :                 CPLFree(pszWhere);
    3701             : 
    3702           0 :             CPLFree(pszConnectionString);
    3703             : 
    3704           0 :             return nullptr;
    3705             :         }
    3706             :     }
    3707           0 :     else if (poSrcDS->nMode == ONE_RASTER_PER_ROW)
    3708             :     {
    3709             :         // one raster per row
    3710             : 
    3711             :         // papszSubdatasets contains name/desc for each subdataset
    3712           0 :         for (int i = 0; i < CSLCount(poSrcDS->papszSubdatasets); i += 2)
    3713             :         {
    3714             :             pszSubdatasetName =
    3715           0 :                 CPLParseNameValue(poSrcDS->papszSubdatasets[i], nullptr);
    3716           0 :             if (pszSubdatasetName == nullptr)
    3717             :             {
    3718           0 :                 CPLDebug("PostGIS_Raster",
    3719             :                          "PostGISRasterDataset::CreateCopy(): "
    3720             :                          "Could not parse name/value out of subdataset list: "
    3721             :                          "%s",
    3722           0 :                          poSrcDS->papszSubdatasets[i]);
    3723           0 :                 continue;
    3724             :             }
    3725             : 
    3726             :             // for each subdataset
    3727           0 :             GDALOpenInfo poOpenInfo(pszSubdatasetName, GA_ReadOnly);
    3728             :             // open the subdataset
    3729           0 :             poSubDS = cpl::down_cast<PostGISRasterDataset *>(Open(&poOpenInfo));
    3730             : 
    3731           0 :             if (poSubDS == nullptr)
    3732             :             {
    3733             :                 // notify!
    3734           0 :                 CPLDebug("PostGIS_Raster",
    3735             :                          "PostGISRasterDataset::CreateCopy(): "
    3736             :                          "Could not open a subdataset: %s",
    3737             :                          pszSubdatasetName);
    3738           0 :                 continue;
    3739             :             }
    3740             : 
    3741             :             // insert one raster
    3742             :             bInsertSuccess =
    3743           0 :                 InsertRaster(poConn, poSubDS, pszSchema, pszTable, pszColumn);
    3744             : 
    3745           0 :             if (!bInsertSuccess)
    3746             :             {
    3747           0 :                 CPLDebug("PostGIS_Raster",
    3748             :                          "PostGISRasterDataset::CreateCopy(): "
    3749             :                          "Could not copy raster subdataset to new dataset.");
    3750             : 
    3751             :                 // keep trying ...
    3752             :             }
    3753             : 
    3754             :             // close this dataset
    3755           0 :             GDALClose(GDALDataset::ToHandle(poSubDS));
    3756             :         }
    3757             :     }
    3758             : 
    3759             :     // commit transaction
    3760           0 :     poResult = PQexec(poConn, "commit");
    3761           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3762             :     {
    3763           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3764             :                  "Error committing database transaction: %s",
    3765             :                  PQerrorMessage(poConn));
    3766           0 :         if (poResult != nullptr)
    3767           0 :             PQclear(poResult);
    3768           0 :         if (pszSchema)
    3769           0 :             CPLFree(pszSchema);
    3770           0 :         if (pszTable)
    3771           0 :             CPLFree(pszTable);
    3772           0 :         if (pszColumn)
    3773           0 :             CPLFree(pszColumn);
    3774           0 :         if (pszWhere)
    3775           0 :             CPLFree(pszWhere);
    3776             : 
    3777           0 :         CPLFree(pszConnectionString);
    3778             : 
    3779           0 :         return nullptr;
    3780             :     }
    3781             : 
    3782           0 :     PQclear(poResult);
    3783             : 
    3784           0 :     if (pszSchema)
    3785           0 :         CPLFree(pszSchema);
    3786           0 :     if (pszTable)
    3787           0 :         CPLFree(pszTable);
    3788           0 :     if (pszColumn)
    3789           0 :         CPLFree(pszColumn);
    3790           0 :     if (pszWhere)
    3791           0 :         CPLFree(pszWhere);
    3792             : 
    3793           0 :     CPLFree(pszConnectionString);
    3794             : 
    3795           0 :     CPLDebug("PostGIS_Raster",
    3796             :              "PostGISRasterDataset::CreateCopy(): "
    3797             :              "Opening new dataset: %s",
    3798             :              pszFilename);
    3799             : 
    3800             :     // connect to the new dataset
    3801           0 :     GDALOpenInfo poOpenInfo(pszFilename, GA_Update);
    3802             :     // open the newdataset
    3803           0 :     poSubDS = cpl::down_cast<PostGISRasterDataset *>(Open(&poOpenInfo));
    3804             : 
    3805           0 :     if (poSubDS == nullptr)
    3806             :     {
    3807           0 :         CPLDebug("PostGIS_Raster", "PostGISRasterDataset::CreateCopy(): "
    3808             :                                    "New dataset could not be opened.");
    3809             :     }
    3810             : 
    3811           0 :     return poSubDS;
    3812             : }
    3813             : 
    3814             : /********************************************************
    3815             :  * \brief Helper method to insert a new raster.
    3816             :  ********************************************************/
    3817           0 : GBool PostGISRasterDataset::InsertRaster(PGconn *poConn,
    3818             :                                          PostGISRasterDataset *poSrcDS,
    3819             :                                          const char *pszSchema,
    3820             :                                          const char *pszTable,
    3821             :                                          const char *pszColumn)
    3822             : {
    3823           0 :     CPLString osCommand;
    3824           0 :     PGresult *poResult = nullptr;
    3825             : 
    3826           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    3827           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    3828           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    3829           0 :     CPLString osSrcSchemaI(CPLQuotedSQLIdentifier(poSrcDS->pszSchema));
    3830           0 :     CPLString osSrcTableI(CPLQuotedSQLIdentifier(poSrcDS->pszTable));
    3831           0 :     CPLString osSrcColumnI(CPLQuotedSQLIdentifier(poSrcDS->pszColumn));
    3832             : 
    3833           0 :     if (poSrcDS->pszWhere == nullptr)
    3834             :     {
    3835             :         osCommand.Printf("insert into %s.%s (%s) (select %s from %s.%s)",
    3836             :                          osSchemaI.c_str(), osTableI.c_str(), osColumnI.c_str(),
    3837             :                          osSrcColumnI.c_str(), osSrcSchemaI.c_str(),
    3838           0 :                          osSrcTableI.c_str());
    3839             :     }
    3840             :     else
    3841             :     {
    3842             :         osCommand.Printf(
    3843             :             "insert into %s.%s (%s) (select %s from %s.%s where %s)",
    3844             :             osSchemaI.c_str(), osTableI.c_str(), osColumnI.c_str(),
    3845             :             osSrcColumnI.c_str(), osSrcSchemaI.c_str(), osSrcTableI.c_str(),
    3846           0 :             poSrcDS->pszWhere);
    3847             :     }
    3848             : 
    3849             : #ifdef DEBUG_QUERY
    3850             :     CPLDebug("PostGIS_Raster",
    3851             :              "PostGISRasterDataset::InsertRaster(): Query = %s",
    3852             :              osCommand.c_str());
    3853             : #endif
    3854             : 
    3855           0 :     poResult = PQexec(poConn, osCommand.c_str());
    3856           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3857             :     {
    3858             : 
    3859           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error inserting raster: %s",
    3860             :                  PQerrorMessage(poConn));
    3861           0 :         if (poResult != nullptr)
    3862           0 :             PQclear(poResult);
    3863             : 
    3864           0 :         return false;
    3865             :     }
    3866             : 
    3867           0 :     PQclear(poResult);
    3868             : 
    3869           0 :     return true;
    3870             : }
    3871             : 
    3872             : /*********************************************************
    3873             :  * \brief Delete a PostGIS Raster dataset.
    3874             :  *********************************************************/
    3875           0 : CPLErr PostGISRasterDataset::Delete(const char *pszFilename)
    3876             : {
    3877           0 :     char *pszSchema = nullptr;
    3878           0 :     char *pszTable = nullptr;
    3879           0 :     char *pszColumn = nullptr;
    3880           0 :     char *pszWhere = nullptr;
    3881             :     GBool bBrowseDatabase;
    3882           0 :     char *pszConnectionString = nullptr;
    3883             :     WorkingMode nMode;
    3884             :     OutDBResolution eOutDBResolution;
    3885           0 :     PGconn *poConn = nullptr;
    3886           0 :     CPLString osCommand;
    3887           0 :     CPLErr nError = CE_Failure;
    3888             : 
    3889             :     // Check connection string
    3890           0 :     if (pszFilename == nullptr || !STARTS_WITH_CI(pszFilename, "PG:"))
    3891             :     {
    3892             :         /**
    3893             :          * The connection string provided is not a valid connection
    3894             :          * string.
    3895             :          */
    3896           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3897             :                  "PostGIS Raster driver was unable to parse the provided "
    3898             :                  "connection string. Nothing was deleted.");
    3899           0 :         return CE_Failure;
    3900             :     }
    3901             : 
    3902           0 :     poConn = GetConnection(pszFilename, &pszConnectionString, &pszSchema,
    3903             :                            &pszTable, &pszColumn, &pszWhere, &nMode,
    3904             :                            &bBrowseDatabase, &eOutDBResolution);
    3905           0 :     if (poConn == nullptr || pszSchema == nullptr || pszTable == nullptr)
    3906             :     {
    3907           0 :         CPLFree(pszConnectionString);
    3908           0 :         CPLFree(pszSchema);
    3909           0 :         CPLFree(pszTable);
    3910           0 :         CPLFree(pszColumn);
    3911           0 :         CPLFree(pszWhere);
    3912             : 
    3913           0 :         return CE_Failure;
    3914             :     }
    3915             : 
    3916             :     // begin transaction
    3917             :     {
    3918           0 :         PGresult *poResult = PQexec(poConn, "begin");
    3919           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3920             :         {
    3921           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3922             :                      "Error beginning database transaction: %s",
    3923             :                      PQerrorMessage(poConn));
    3924             : 
    3925             :             // set nMode to NO_MODE to avoid any further processing
    3926           0 :             nMode = NO_MODE;
    3927             :         }
    3928             : 
    3929           0 :         PQclear(poResult);
    3930             :     }
    3931             : 
    3932           0 :     if (nMode == ONE_RASTER_PER_TABLE ||
    3933           0 :         (nMode == ONE_RASTER_PER_ROW && pszWhere == nullptr))
    3934             :     {
    3935             :         // without a where clause, this delete command shall delete
    3936             :         // all subdatasets, even if the mode is ONE_RASTER_PER_ROW
    3937             : 
    3938             :         // drop table <schema>.<table>;
    3939           0 :         osCommand.Printf("drop table %s.%s", pszSchema, pszTable);
    3940           0 :         PGresult *poResult = PQexec(poConn, osCommand.c_str());
    3941           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3942             :         {
    3943           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3944             :                      "Couldn't drop the table %s.%s: %s", pszSchema, pszTable,
    3945             :                      PQerrorMessage(poConn));
    3946             :         }
    3947             :         else
    3948             :         {
    3949           0 :             nError = CE_None;
    3950             :         }
    3951           0 :         if (poResult)
    3952           0 :             PQclear(poResult);
    3953             :     }
    3954           0 :     else if (nMode == ONE_RASTER_PER_ROW)
    3955             :     {
    3956             : 
    3957             :         // delete from <schema>.<table> where <where>
    3958           0 :         osCommand.Printf("delete from %s.%s where %s", pszSchema, pszTable,
    3959           0 :                          pszWhere);
    3960           0 :         PGresult *poResult = PQexec(poConn, osCommand.c_str());
    3961           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3962             :         {
    3963           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3964             :                      "Couldn't delete records from the table %s.%s: %s",
    3965             :                      pszSchema, pszTable, PQerrorMessage(poConn));
    3966             :         }
    3967             :         else
    3968             :         {
    3969           0 :             nError = CE_None;
    3970             :         }
    3971           0 :         if (poResult)
    3972           0 :             PQclear(poResult);
    3973             :     }
    3974             : 
    3975             :     // if mode == NO_MODE, the begin transaction above did not complete,
    3976             :     // so no commit is necessary
    3977           0 :     if (nMode != NO_MODE)
    3978             :     {
    3979           0 :         PGresult *poResult = PQexec(poConn, "commit");
    3980           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3981             :         {
    3982             : 
    3983           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3984             :                      "Error committing database transaction: %s",
    3985             :                      PQerrorMessage(poConn));
    3986             : 
    3987           0 :             nError = CE_Failure;
    3988             :         }
    3989           0 :         if (poResult)
    3990           0 :             PQclear(poResult);
    3991             :     }
    3992             : 
    3993           0 :     CPLFree(pszSchema);
    3994           0 :     CPLFree(pszTable);
    3995           0 :     CPLFree(pszColumn);
    3996           0 :     CPLFree(pszWhere);
    3997             : 
    3998             :     // clean up connection string
    3999           0 :     CPLFree(pszConnectionString);
    4000             : 
    4001           0 :     return nError;
    4002             : }
    4003             : 
    4004             : /***********************************************************************
    4005             :  * \brief Create an array with all the coordinates needed to construct
    4006             :  * a polygon using ST_PolygonFromText.
    4007             :  **********************************************************************/
    4008           0 : GBool PostGISRasterDataset::PolygonFromCoords(int nXOff, int nYOff,
    4009             :                                               int nXEndOff, int nYEndOff,
    4010             :                                               double adfProjWin[8])
    4011             : {
    4012             :     // We first construct a polygon to intersect with
    4013           0 :     int ulx = nXOff;
    4014           0 :     int uly = nYOff;
    4015           0 :     int lrx = nXEndOff;
    4016           0 :     int lry = nYEndOff;
    4017             : 
    4018           0 :     double xRes = adfGeoTransform[GEOTRSFRM_WE_RES];
    4019           0 :     double yRes = adfGeoTransform[GEOTRSFRM_NS_RES];
    4020             : 
    4021           0 :     adfProjWin[0] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + ulx * xRes +
    4022           0 :                     uly * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4023           0 :     adfProjWin[1] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4024           0 :                     ulx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4025           0 :                     uly * yRes;
    4026           0 :     adfProjWin[2] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + lrx * xRes +
    4027           0 :                     uly * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4028           0 :     adfProjWin[3] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4029           0 :                     lrx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4030           0 :                     uly * yRes;
    4031           0 :     adfProjWin[4] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + lrx * xRes +
    4032           0 :                     lry * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4033           0 :     adfProjWin[5] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4034           0 :                     lrx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4035           0 :                     lry * yRes;
    4036           0 :     adfProjWin[6] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + ulx * xRes +
    4037           0 :                     lry * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4038           0 :     adfProjWin[7] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4039           0 :                     ulx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4040           0 :                     lry * yRes;
    4041             : 
    4042             : #ifdef DEBUG_VERBOSE
    4043             :     CPLDebug("PostGIS_Raster",
    4044             :              "PostGISRasterDataset::PolygonFromCoords: constructed "
    4045             :              "polygon: POLYGON((%.17f %.17f, %.17f %.17f, %.17f %.17f, "
    4046             :              "%.17f %.17f, %.17f %.17f))",
    4047             :              adfProjWin[0], adfProjWin[1], adfProjWin[2], adfProjWin[3],
    4048             :              adfProjWin[4], adfProjWin[5], adfProjWin[6], adfProjWin[7],
    4049             :              adfProjWin[0], adfProjWin[1]);
    4050             : #endif
    4051             : 
    4052           0 :     return true;
    4053             : }
    4054             : 
    4055             : /***********************************************************************
    4056             :  * GDALRegister_PostGISRaster()
    4057             :  **********************************************************************/
    4058           9 : void GDALRegister_PostGISRaster()
    4059             : 
    4060             : {
    4061           9 :     if (!GDAL_CHECK_VERSION("PostGISRaster driver"))
    4062           0 :         return;
    4063             : 
    4064           9 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
    4065           0 :         return;
    4066             : 
    4067           9 :     GDALDriver *poDriver = new PostGISRasterDriver();
    4068           9 :     PostGISRasterDriverSetCommonMetadata(poDriver);
    4069             : 
    4070           9 :     poDriver->pfnOpen = PostGISRasterDataset::Open;
    4071           9 :     poDriver->pfnCreateCopy = PostGISRasterDataset::CreateCopy;
    4072           9 :     poDriver->pfnDelete = PostGISRasterDataset::Delete;
    4073             : 
    4074           9 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    4075             : }

Generated by: LCOV version 1.14