LCOV - code coverage report
Current view: top level - frmts/postgisraster - postgisrasterdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 210 1508 13.9 %
Date: 2025-07-11 10:11:13 Functions: 11 43 25.6 %

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

Generated by: LCOV version 1.14