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

Generated by: LCOV version 1.14