• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 15885686134

25 Jun 2025 07:44PM UTC coverage: 71.084%. Remained the same
15885686134

push

github

rouault
gdal_priv.h: fix C++11 compatibility

573814 of 807237 relevant lines covered (71.08%)

250621.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

13.93
/frmts/postgisraster/postgisrasterdataset.cpp
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
PostGISRasterDataset::PostGISRasterDataset()
2✔
88
    : VRTDataset(0, 0), papszSubdatasets(nullptr), nSrid(-1),
89
      nOverviewFactor(1), nBandsToCreate(0), poConn(nullptr),
90
      bRegularBlocking(false), bAllTilesSnapToSameGrid(false),
91
      bCheckAllTiles(
92
          CPLTestBool(CPLGetConfigOption("PR_ALLOW_WHOLE_TABLE_SCAN", "YES"))),
2✔
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
      nTileWidth(0), nTileHeight(0)
4✔
106
{
107
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2✔
108

109
    m_gt[GEOTRSFRM_TOPLEFT_X] = 0.0;
2✔
110
    m_gt[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
2✔
111
    m_gt[GEOTRSFRM_TOPLEFT_Y] = 0.0;
2✔
112
    m_gt[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
2✔
113
    // coverity[tainted_data]
114
    m_gt[GEOTRSFRM_WE_RES] =
4✔
115
        CPLAtof(CPLGetConfigOption("PR_WE_RES", NO_VALID_RES));
2✔
116
    // coverity[tainted_data]
117
    m_gt[GEOTRSFRM_NS_RES] =
4✔
118
        CPLAtof(CPLGetConfigOption("PR_NS_RES", NO_VALID_RES));
2✔
119

120
    const char *pszTmp = nullptr;
2✔
121
    // We ignore this option if we provided the desired resolution
122
    if (CPLIsEqual(m_gt[GEOTRSFRM_WE_RES], CPLAtof(NO_VALID_RES)) ||
2✔
123
        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
        m_gt[GEOTRSFRM_WE_RES] = 0.0;
2✔
128
        m_gt[GEOTRSFRM_NS_RES] = 0.0;
2✔
129

130
        pszTmp = CPLGetConfigOption("PR_RESOLUTION_STRATEGY", "AVERAGE_APPROX");
2✔
131

132
        if (EQUAL(pszTmp, "LOWEST"))
2✔
133
            resolutionStrategy = LOWEST_RESOLUTION;
×
134

135
        else if (EQUAL(pszTmp, "HIGHEST"))
2✔
136
            resolutionStrategy = HIGHEST_RESOLUTION;
×
137

138
        else if (EQUAL(pszTmp, "USER"))
2✔
139
            resolutionStrategy = USER_RESOLUTION;
×
140

141
        else if (EQUAL(pszTmp, "AVERAGE"))
2✔
142
            resolutionStrategy = AVERAGE_RESOLUTION;
×
143
    }
144
    else
145
    {
146
        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
    poDriver = nullptr;
2✔
160

161
    nRasterXSize = 0;
2✔
162
    nRasterYSize = 0;
2✔
163

164
    SetWritable(false);
2✔
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
PostGISRasterDataset::~PostGISRasterDataset()
4✔
177
{
178

179
    if (pszSchema)
2✔
180
    {
181
        CPLFree(pszSchema);
2✔
182
        pszSchema = nullptr;
2✔
183
    }
184

185
    if (pszTable)
2✔
186
    {
187
        CPLFree(pszTable);
2✔
188
        pszTable = nullptr;
2✔
189
    }
190

191
    if (pszColumn)
2✔
192
    {
193
        CPLFree(pszColumn);
2✔
194
        pszColumn = nullptr;
2✔
195
    }
196

197
    if (pszWhere)
2✔
198
    {
199
        CPLFree(pszWhere);
×
200
        pszWhere = nullptr;
×
201
    }
202

203
    if (pszPrimaryKeyName)
2✔
204
    {
205
        CPLFree(pszPrimaryKeyName);
×
206
        pszPrimaryKeyName = nullptr;
×
207
    }
208

209
    if (papszSubdatasets)
2✔
210
    {
211
        CSLDestroy(papszSubdatasets);
×
212
        papszSubdatasets = nullptr;
×
213
    }
214

215
    if (hQuadTree)
2✔
216
    {
217
        CPLQuadTreeDestroy(hQuadTree);
×
218
        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
    PostGISRasterDataset::CloseDependentDatasets();
2✔
227

228
    if (papoSourcesHolders)
2✔
229
    {
230
        int i;
231
        for (i = 0; i < m_nTiles; i++)
×
232
        {
233
            if (papoSourcesHolders[i])
×
234
                delete papoSourcesHolders[i];
×
235
        }
236

237
        VSIFree(papoSourcesHolders);
×
238
        papoSourcesHolders = nullptr;
×
239
    }
240
}
4✔
241

242
/************************************************************************/
243
/*                        CloseDependentDatasets()                      */
244
/************************************************************************/
245

246
int PostGISRasterDataset::CloseDependentDatasets()
2✔
247
{
248
    int bHasDroppedRef = VRTDataset::CloseDependentDatasets();
2✔
249
    if (nOverviewCount > 0)
2✔
250
    {
251
        int i;
252
        for (i = 0; i < nOverviewCount; i++)
×
253
        {
254
            delete papoOverviewDS[i];
×
255
        }
256
        CPLFree(papoOverviewDS);
×
257
        papoOverviewDS = nullptr;
×
258
        nOverviewCount = 0;
×
259
        bHasDroppedRef = TRUE;
×
260
    }
261
    if (!oOutDBDatasetCache.empty())
2✔
262
    {
263
        oOutDBDatasetCache.clear();
×
264
        bHasDroppedRef = TRUE;
×
265
    }
266

267
    return bHasDroppedRef;
2✔
268
}
269

270
/************************************************************************/
271
/*                            FlushCache()                              */
272
/************************************************************************/
273

274
CPLErr PostGISRasterDataset::FlushCache(bool bAtClosing)
2✔
275
{
276
    const CPLErr eErr = VRTDataset::FlushCache(bAtClosing);
2✔
277
    oOutDBDatasetCache.clear();
2✔
278
    return eErr;
2✔
279
}
280

281
/************************************************************************/
282
/*                            HasSpatialIndex()                         */
283
/************************************************************************/
284

285
GBool PostGISRasterDataset::HasSpatialIndex()
×
286
{
287
    CPLString osCommand;
×
288
    PGresult *poResult = nullptr;
×
289

290
    // If exists, return it
291
    if (bHasTriedHasSpatialIndex)
×
292
    {
293
        return bHasSpatialIndex;
×
294
    }
295

296
    bHasTriedHasSpatialIndex = true;
×
297

298
    /* For debugging purposes only */
299
    if (CPLTestBool(CPLGetConfigOption("PR_DISABLE_GIST", "FALSE")))
×
300
        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
        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
    poResult = PQexec(poConn, osCommand.c_str());
×
330

331
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
332
        PQntuples(poResult) <= 0)
×
333
    {
334
        bHasSpatialIndex = false;
×
335
        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
        bHasSpatialIndex = true;
×
345
    }
346

347
    if (poResult)
×
348
        PQclear(poResult);
×
349
    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
const char *PostGISRasterDataset::GetPrimaryKeyRef()
×
358
{
359
    CPLString osCommand;
×
360
    PGresult *poResult = nullptr;
×
361

362
    // If exists, return it
363
    if (bHasTriedFetchingPrimaryKeyName)
×
364
    {
365
        return pszPrimaryKeyName;
×
366
    }
367

368
    bHasTriedFetchingPrimaryKeyName = true;
×
369

370
    /* For debugging purposes only */
371
    if (CPLTestBool(CPLGetConfigOption("PR_DISABLE_PK", "FALSE")))
×
372
        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
        pszSchema, pszTable);
×
384

385
#ifdef DEBUG_QUERY
386
    CPLDebug("PostGIS_Raster",
387
             "PostGISRasterDataset::GetPrimaryKeyRef(): Query: %s",
388
             osCommand.c_str());
389
#endif
390

391
    poResult = PQexec(poConn, osCommand.c_str());
×
392

393
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
394
        PQntuples(poResult) <= 0)
×
395
    {
396

397
        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
            pszSchema, pszTable);
×
411

412
#ifdef DEBUG_QUERY
413
        CPLDebug("PostGIS_Raster",
414
                 "PostGISRasterDataset::GetPrimaryKeyRef(): Query: %s",
415
                 osCommand.c_str());
416
#endif
417

418
        poResult = PQexec(poConn, osCommand.c_str());
×
419

420
        if (poResult == nullptr ||
×
421
            PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
422
            PQntuples(poResult) <= 0)
×
423
        {
424

425
            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
            pszPrimaryKeyName = nullptr;  // Just in case
×
433
        }
434
        else
435
        {
436
            pszPrimaryKeyName = CPLStrdup(PQgetvalue(poResult, 0, 0));
×
437
        }
438
    }
439

440
    // Ok, get the primary key
441
    else
442
    {
443
        pszPrimaryKeyName = CPLStrdup(PQgetvalue(poResult, 0, 0));
×
444
        bIsFastPK = true;
×
445
    }
446

447
    PQclear(poResult);
×
448

449
    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
GBool PostGISRasterDataset::BrowseDatabase(const char *pszCurrentSchema,
×
467
                                           const char *pszValidConnectionString)
468
{
469

470
    char *l_pszSchema = nullptr;
×
471
    char *l_pszTable = nullptr;
×
472
    char *l_pszColumn = nullptr;
×
473

474
    int i = 0;
×
475
    int nTuples = 0;
×
476
    PGresult *poResult = nullptr;
×
477
    CPLString osCommand;
×
478

479
    /*************************************************************
480
     * Fetch all the raster tables and store them as subdatasets
481
     *************************************************************/
482
    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
            "pg_type.typname = 'raster'");
×
492

493
        poResult = PQexec(poConn, osCommand.c_str());
×
494
        if (poResult == nullptr ||
×
495
            PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
496
            PQntuples(poResult) <= 0)
×
497
        {
498

499
            ReportError(CE_Failure, CPLE_AppDefined,
×
500
                        "Error browsing database for PostGIS Raster tables: %s",
501
                        PQerrorMessage(poConn));
×
502
            if (poResult != nullptr)
×
503
                PQclear(poResult);
×
504

505
            return false;
×
506
        }
507

508
        nTuples = PQntuples(poResult);
×
509
        for (i = 0; i < nTuples; i++)
×
510
        {
511
            l_pszSchema = PQgetvalue(poResult, i, 0);
×
512
            l_pszTable = PQgetvalue(poResult, i, 1);
×
513
            l_pszColumn = PQgetvalue(poResult, i, 2);
×
514

515
            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
            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
        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
                         pszCurrentSchema);
×
544

545
        poResult = PQexec(poConn, osCommand.c_str());
×
546
        if (poResult == nullptr ||
×
547
            PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
548
            PQntuples(poResult) <= 0)
×
549
        {
550

551
            ReportError(CE_Failure, CPLE_AppDefined,
×
552
                        "Error browsing database for PostGIS Raster tables: %s",
553
                        PQerrorMessage(poConn));
×
554

555
            if (poResult != nullptr)
×
556
                PQclear(poResult);
×
557

558
            return false;
×
559
        }
560

561
        nTuples = PQntuples(poResult);
×
562
        for (i = 0; i < nTuples; i++)
×
563
        {
564
            l_pszTable = PQgetvalue(poResult, i, 0);
×
565
            l_pszColumn = PQgetvalue(poResult, i, 1);
×
566

567
            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
            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
        PQclear(poResult);
×
580
    }
581

582
    return true;
×
583
}
584

585
/***********************************************************************
586
 * \brief Look for overview tables for the bands of the current dataset
587
 **********************************************************************/
588
PROverview *PostGISRasterDataset::GetOverviewTables(int *pnOverviews)
×
589
{
590
    PROverview *poOV = nullptr;
×
591
    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
                     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
    PGresult *poResult = PQexec(poConn, osCommand.c_str());
×
606

607
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
608
        PQntuples(poResult) < 0)
×
609
    {
610

611
        ReportError(CE_Failure, CPLE_AppDefined,
×
612
                    "Error looking for overview tables: %s",
613
                    PQerrorMessage(poConn));
×
614

615
        if (poResult)
×
616
            PQclear(poResult);
×
617

618
        return nullptr;
×
619
    }
620

621
    else if (PQntuples(poResult) == 0)
×
622
    {
623
        CPLDebug("PostGIS_Raster",
×
624
                 "PostGISRasterDataset::GetOverviewTables(): No overviews "
625
                 "for table %s.%s",
626
                 pszTable, pszSchema);
627

628
        if (poResult)
×
629
            PQclear(poResult);
×
630

631
        return nullptr;
×
632
    }
633

634
    int nTuples = PQntuples(poResult);
×
635

636
    poOV = static_cast<PROverview *>(VSIMalloc2(nTuples, sizeof(PROverview)));
×
637
    if (poOV == nullptr)
×
638
    {
639
        ReportError(CE_Failure, CPLE_AppDefined,
×
640
                    "Error looking for overview tables");
641

642
        PQclear(poResult);
×
643

644
        return nullptr;
×
645
    }
646

647
    int iOVerview = 0;
×
648
    for (iOVerview = 0; iOVerview < nTuples; iOVerview++)
×
649
    {
650
        poOV[iOVerview].pszSchema =
×
651
            CPLStrdup(PQgetvalue(poResult, iOVerview, 3));
×
652

653
        poOV[iOVerview].pszTable =
×
654
            CPLStrdup(PQgetvalue(poResult, iOVerview, 0));
×
655

656
        poOV[iOVerview].pszColumn =
×
657
            CPLStrdup(PQgetvalue(poResult, iOVerview, 2));
×
658

659
        poOV[iOVerview].nFactor = atoi(PQgetvalue(poResult, iOVerview, 1));
×
660
    }
661

662
    if (pnOverviews)
×
663
        *pnOverviews = nTuples;
×
664

665
    PQclear(poResult);
×
666

667
    return poOV;
×
668
}
669

670
/***********************************************************************
671
 * \brief Build overview datasets
672
 ***********************************************************************/
673
void PostGISRasterDataset::BuildOverviews()
×
674
{
675
    if (bHasBuiltOverviews || poParentDS != nullptr)
×
676
        return;
×
677

678
    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
    int nOV = 0;
×
684
    PROverview *poOV = GetOverviewTables(&nOV);
×
685

686
    if (poOV)
×
687
    {
688
        papoOverviewDS = static_cast<PostGISRasterDataset **>(
×
689
            CPLCalloc(nOV, sizeof(PostGISRasterDataset *)));
×
690
        nOverviewCount = 0;
×
691

692
        int iOV;
693
        for (iOV = 0; iOV < nOV; iOV++)
×
694
        {
695
            PostGISRasterDataset *poOvrDS = new PostGISRasterDataset();
×
696
            poOvrDS->ShareLockWithParentDataset(this);
×
697
            poOvrDS->nOverviewFactor = poOV[iOV].nFactor;
×
698
            poOvrDS->poConn = poConn;
×
699
            poOvrDS->eAccess = eAccess;
×
700
            poOvrDS->eOutDBResolution = eOutDBResolution;
×
701
            poOvrDS->bHasStBandFileSize = bHasStBandFileSize;
×
702
            poOvrDS->nMode = nMode;
×
703
            poOvrDS->pszSchema = poOV[iOV].pszSchema;  // takes ownership
×
704
            poOvrDS->pszTable = poOV[iOV].pszTable;    // takes ownership
×
705
            poOvrDS->pszColumn = poOV[iOV].pszColumn;  // takes ownership
×
706
            poOvrDS->pszWhere = pszWhere ? CPLStrdup(pszWhere) : nullptr;
×
707
            poOvrDS->poParentDS = this;
×
708

709
            if (!CPLTestBool(
×
710
                    CPLGetConfigOption("PG_DEFERRED_OVERVIEWS", "YES")) &&
×
711
                (!poOvrDS->SetRasterProperties(nullptr) ||
×
712
                 poOvrDS->GetRasterCount() != GetRasterCount()))
×
713
            {
714
                delete poOvrDS;
×
715
            }
716
            else
717
            {
718
                papoOverviewDS[nOverviewCount++] = poOvrDS;
×
719
            }
720
        }
721

722
        VSIFree(poOV);
×
723
    }
724
}
725

726
/***********************************************************************
727
 * \brief Returns overview count
728
 ***********************************************************************/
729
int PostGISRasterDataset::GetOverviewCount()
×
730
{
731
    BuildOverviews();
×
732
    return nOverviewCount;
×
733
}
734

735
/***********************************************************************
736
 * \brief Returns overview dataset
737
 ***********************************************************************/
738
PostGISRasterDataset *PostGISRasterDataset::GetOverviewDS(int iOvr)
×
739
{
740
    if (iOvr < 0 || iOvr > GetOverviewCount())
×
741
        return nullptr;
×
742
    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
void PostGISRasterDataset::GetDstWin(PostGISRasterTileDataset *psDP,
×
753
                                     int *pnDstXOff, int *pnDstYOff,
754
                                     int *pnDstXSize, int *pnDstYSize)
755
{
756
    double we_res = this->m_gt[GEOTRSFRM_WE_RES];
×
757
    double ns_res = this->m_gt[GEOTRSFRM_NS_RES];
×
758

759
    GDALGeoTransform tileGT;
×
760
    psDP->GetGeoTransform(tileGT);
×
761

762
    *pnDstXOff =
×
763
        static_cast<int>(0.5 + (tileGT[GEOTRSFRM_TOPLEFT_X] - xmin) / we_res);
×
764

765
    if (ns_res < 0)
×
766
        *pnDstYOff = static_cast<int>(
×
767
            0.5 + (ymax - tileGT[GEOTRSFRM_TOPLEFT_Y]) / -ns_res);
×
768
    else
769
        *pnDstYOff = static_cast<int>(
×
770
            0.5 + (tileGT[GEOTRSFRM_TOPLEFT_Y] - ymin) / ns_res);
×
771

772
    *pnDstXSize = static_cast<int>(0.5 + psDP->GetRasterXSize() *
×
773
                                             tileGT[GEOTRSFRM_WE_RES] / we_res);
×
774
    *pnDstYSize = static_cast<int>(0.5 + psDP->GetRasterYSize() *
×
775
                                             tileGT[GEOTRSFRM_NS_RES] / ns_res);
×
776
}
×
777

778
/***********************************************************************
779
 * \brief Add tiles bands as complex source for raster bands.
780
 **********************************************************************/
781
void PostGISRasterDataset::AddComplexSource(PostGISRasterTileDataset *poRTDS)
×
782
{
783
    // Parameters to add the tile bands as sources
784
    int nDstXOff = 0;
×
785
    int nDstYOff = 0;
×
786
    int nDstXSize = 0;
×
787
    int nDstYSize = 0;
×
788

789
    // Get src and dst parameters
790
    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
    for (int iBand = 0; iBand < nBandsToCreate; iBand++)
×
804
    {
805
        PostGISRasterRasterBand *prb =
806
            cpl::down_cast<PostGISRasterRasterBand *>(GetRasterBand(iBand + 1));
×
807

808
        int bHasNoData = FALSE;
×
809
        double dfBandNoDataValue = prb->GetNoDataValue(&bHasNoData);
×
810

811
        PostGISRasterTileRasterBand *prtb =
812
            cpl::down_cast<PostGISRasterTileRasterBand *>(
×
813
                poRTDS->GetRasterBand(iBand + 1));
814

815
        prb->AddComplexSource(
×
816
            prtb, 0, 0, poRTDS->GetRasterXSize(), poRTDS->GetRasterYSize(),
×
817
            nDstXOff, nDstYOff, nDstXSize, nDstYSize, 0.0, 1.0,
818
            (bHasNoData) ? dfBandNoDataValue : VRT_NODATA_UNSET);
×
819

820
        prtb->poSource = prb->papoSources[prb->nSources - 1];
×
821
    }
822
}
×
823

824
/************************************************************************/
825
/*                         GetMatchingSourceRef()                       */
826
/************************************************************************/
827

828
/**
829
 * \brief Get the tile dataset that matches the given upper left pixel
830
 **/
831
PostGISRasterTileDataset *
832
PostGISRasterDataset::GetMatchingSourceRef(double dfUpperLeftX,
×
833
                                           double dfUpperLeftY)
834
{
835
    int i;
836
    PostGISRasterTileDataset *poRTDS = nullptr;
×
837

838
    for (i = 0; i < m_nTiles; i++)
×
839
    {
840
        poRTDS = papoSourcesHolders[i];
×
841

842
        if (CPLIsEqual(poRTDS->m_gt[GEOTRSFRM_TOPLEFT_X], dfUpperLeftX) &&
×
843
            CPLIsEqual(poRTDS->m_gt[GEOTRSFRM_TOPLEFT_Y], dfUpperLeftY))
×
844
        {
845

846
            return poRTDS;
×
847
        }
848
    }
849

850
    return nullptr;
×
851
}
852

853
/************************************************************************/
854
/*                           CacheTile()                                */
855
/************************************************************************/
856
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
    char *pszRes = CPLStrdup(pszMetadata);
×
864

865
    // Skip first "("
866
    char *pszFilteredRes = pszRes + 1;
×
867

868
    // Skip last ")"
869
    pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
×
870

871
    // Tokenize
872
    char **papszParams = CSLTokenizeString2(
×
873
        pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
874
    CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_METADATA_RECORD);
×
875

876
    CPLFree(pszRes);
×
877

878
    const double dfTileUpperLeftX = CPLAtof(papszParams[POS_UPPERLEFTX]);
×
879
    const double dfTileUpperLeftY = CPLAtof(papszParams[POS_UPPERLEFTY]);
×
880
    const double dfTileResX = CPLAtof(papszParams[POS_SCALEX]);
×
881
    const double dfTileResY = CPLAtof(papszParams[POS_SCALEY]);
×
882
    const int nTileXSize = atoi(papszParams[POS_WIDTH]);
×
883
    const int nTileYSize = atoi(papszParams[POS_HEIGHT]);
×
884

885
    CSLDestroy(papszParams);
×
886
    papszParams = nullptr;
×
887

888
    /**
889
     * Get actual raster band data
890
     **/
891
    const GDALDataType eDT = GetRasterBand(nBand)->GetRasterDataType();
×
892
    const int nBandDataTypeSize = GDALGetDataTypeSizeBytes(eDT);
×
893
    const int nExpectedBandDataSize =
×
894
        nTileXSize * nTileYSize * nBandDataTypeSize;
×
895
    const int nExpectedBands = bAllBandCaching ? GetRasterCount() : 1;
×
896

897
    int nWKBLength = 0;
×
898

899
    struct CPLFreer
900
    {
901
        void operator()(GByte *x) const
×
902
        {
903
            CPLFree(x);
×
904
        }
×
905
    };
906

907
    std::unique_ptr<GByte, CPLFreer> pbyDataAutoFreed(
908
        CPLHexToBinary(pszRaster, &nWKBLength));
×
909
    GByte *pbyData = pbyDataAutoFreed.get();
×
910
    const int nMinimumWKBLength =
×
911
        RASTER_HEADER_SIZE + BAND_SIZE(1, nBandDataTypeSize) * nExpectedBands;
×
912
    if (nWKBLength < nMinimumWKBLength)
×
913
    {
914
        CPLDebug("PostGIS_Raster",
×
915
                 "nWKBLength=%d. too short. Expected at least %d", nWKBLength,
916
                 nMinimumWKBLength);
917
        return;
×
918
    }
919

920
    // Do byte-swapping if necessary */
921
    const bool bIsLittleEndian = (pbyData[0] == 1);
×
922
#ifdef CPL_LSB
923
    const bool bSwap = !bIsLittleEndian;
×
924
#else
925
    const bool bSwap = bIsLittleEndian;
926
#endif
927

928
    PostGISRasterTileDataset *poRTDS = nullptr;
×
929
    if (GetPrimaryKeyRef() != nullptr)
×
930
        poRTDS = GetMatchingSourceRef(pszPKID);
×
931
    else
932
        poRTDS = GetMatchingSourceRef(dfTileUpperLeftX, dfTileUpperLeftY);
×
933
    if (poRTDS == nullptr)
×
934
    {
935
        return;
×
936
    }
937

938
    int nCurOffset = RASTER_HEADER_SIZE;
×
939
    for (int k = 1; k <= nExpectedBands; k++)
×
940
    {
941
        /**
942
         * Get the right PostGISRasterRasterBand
943
         **/
944
        int nCurBand = (nExpectedBands > 1) ? k : nBand;
×
945

946
        /**
947
         * Get the right tileband
948
         **/
949
        GDALRasterBand *poRTB = poRTDS->GetRasterBand(nCurBand);
×
950
        if (poRTB == nullptr)
×
951
            return;
×
952

953
        // For each band we have at least the flag byte, the nodata value
954
        if (nWKBLength < nCurOffset + 1 + nBandDataTypeSize)
×
955
        {
956
            CPLDebug("PostGIS_Raster",
×
957
                     "nWKBLength=%d, not enough data for band %d", nWKBLength,
958
                     k);
959
            return;
×
960
        }
961

962
        // Is it indb-raster ?
963
        if ((pbyData[nCurOffset] & 0x80) == 0)
×
964
        {
965
            nCurOffset += 1 + nBandDataTypeSize;
×
966
            if (nWKBLength < nCurOffset + nExpectedBandDataSize)
×
967
            {
968
                CPLDebug("PostGIS_Raster",
×
969
                         "nWKBLength=%d, not enough data for band %d",
970
                         nWKBLength, k);
971
                return;
×
972
            }
973

974
            GByte *pbyDataToRead = pbyData + nCurOffset;
×
975
            nCurOffset += nExpectedBandDataSize;
×
976

977
            if (bSwap && nBandDataTypeSize > 1)
×
978
            {
979
                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
            GDALRasterBlock *poBlock = poRTB->GetLockedBlockRef(0, 0, TRUE);
×
988
            if (poBlock != nullptr)
×
989
            {
990
                // Point block data ref to fetched data
991
                memcpy(poBlock->GetDataRef(), pbyDataToRead,
×
992
                       nExpectedBandDataSize);
993

994
                poBlock->DropLock();
×
995
            }
996
        }
997
        else
998
        {
999
            /**
1000
             * Manually add each tile data to the cache of the
1001
             * matching PostGISRasterTileRasterBand.
1002
             **/
1003
            GDALRasterBlock *poBlock = poRTB->GetLockedBlockRef(0, 0, TRUE);
×
1004
            if (poBlock == nullptr)
×
1005
                return;
×
1006
            if (!LoadOutdbRaster(nCurOffset, eDT, k, pbyData, nWKBLength,
×
1007
                                 poBlock->GetDataRef(), dfTileUpperLeftX,
1008
                                 dfTileUpperLeftY, dfTileResX, dfTileResY,
1009
                                 nTileXSize, nTileYSize))
1010
            {
1011
                poBlock->DropLock();
×
1012
                return;
×
1013
            }
1014
            poBlock->DropLock();
×
1015
        }
1016
    }
1017

1018
    if (nCurOffset != nWKBLength)
×
1019
    {
1020
        CPLDebug("PostGIS_Raster",
×
1021
                 "Trailing bytes at end of serialized raster");
1022
        return;
×
1023
    }
1024
}
1025

1026
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
    const int nBandDataTypeSize = GDALGetDataTypeSizeBytes(eDT);
×
1035

1036
    nCurOffset += 1 + nBandDataTypeSize;
×
1037
    if (nWKBLength < nCurOffset + 1 + 1)
×
1038
    {
1039
        CPLDebug("PostGIS_Raster", "nWKBLength=%d, not enough data for band %d",
×
1040
                 nWKBLength, nBand);
1041
        return false;
×
1042
    }
1043
    // Postgis raster outdb band numbering starts at 0
1044
    GByte nOutdbBandNumber = 1 + pbyData[nCurOffset];
×
1045
    nCurOffset++;
×
1046
    CPLString osPath;
×
1047
    for (int i = 0; nCurOffset + i < nWKBLength; i++)
×
1048
    {
1049
        if (pbyData[nCurOffset + i] == '\0')
×
1050
        {
1051
            osPath.assign(reinterpret_cast<const char *>(pbyData) + nCurOffset,
×
1052
                          i);
×
1053
            nCurOffset += i + 1;
×
1054
            break;
×
1055
        }
1056
    }
1057
    if (osPath.empty())
×
1058
    {
1059
        CPLDebug("PostGIS_Raster",
×
1060
                 "nWKBLength=%d, not enough data for outdb raster band %d",
1061
                 nWKBLength, nBand);
1062
        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
    std::shared_ptr<GDALDataset> poDS;
×
1069
    if (!oOutDBDatasetCache.tryGet(osPath, poDS))
×
1070
    {
1071
        poDS.reset(GDALDataset::Open(osPath, GDAL_OF_RASTER));
×
1072
        if (poDS == nullptr)
×
1073
        {
1074
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
×
1075
                     osPath.c_str());
1076
            return false;
×
1077
        }
1078
        oOutDBDatasetCache.insert(osPath, poDS);
×
1079
    }
1080

1081
    if (nOutdbBandNumber > poDS->GetRasterCount())
×
1082
    {
1083
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid band number %d for %s",
×
1084
                 nOutdbBandNumber, osPath.c_str());
1085
        return false;
×
1086
    }
1087
    GDALGeoTransform gt;
×
1088
    poDS->GetGeoTransform(gt);
×
1089
    int nXOff =
1090
        static_cast<int>(std::round((dfTileUpperLeftX - gt[0]) / gt[1]));
×
1091
    int nYOff =
1092
        static_cast<int>(std::round((dfTileUpperLeftY - gt[3]) / gt[5]));
×
1093
    int nXOff2 = static_cast<int>(std::round(
×
1094
        (dfTileUpperLeftX + nTileXSize * dfTileResX - gt[0]) / gt[1]));
×
1095
    int nYOff2 = static_cast<int>(std::round(
×
1096
        (dfTileUpperLeftY + nTileYSize * dfTileResY - gt[3]) / gt[5]));
×
1097
    int nSrcXSize = nXOff2 - nXOff;
×
1098
    int nSrcYSize = nYOff2 - nYOff;
×
1099
    if (nXOff < 0 || nYOff < 0 || nXOff2 > poDS->GetRasterXSize() ||
×
1100
        nYOff2 > poDS->GetRasterYSize())
×
1101
    {
1102
        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
        return false;
×
1107
    }
1108

1109
    // Point block data ref to fetched data
1110
    return poDS->GetRasterBand(nOutdbBandNumber)
1111
               ->RasterIO(GF_Read, nXOff, nYOff, nSrcXSize, nSrcYSize, pImage,
×
1112
                          nTileXSize, nTileYSize, eDT, 0, 0,
1113
                          nullptr) == CE_None;
×
1114
}
1115

1116
/************************************************************************/
1117
/*                          LoadSources()                               */
1118
/************************************************************************/
1119

1120
GBool PostGISRasterDataset::LoadSources(int nXOff, int nYOff, int nXSize,
×
1121
                                        int nYSize, int nBand)
1122
{
1123
    if (!bBuildQuadTreeDynamically)
×
1124
        return false;
×
1125

1126
    CPLString osSpatialFilter;
×
1127
    CPLString osIDsToFetch;
×
1128
    int nYSizeToQuery = nYSize;
×
1129

1130
    bool bFetchAll = false;
×
1131
    if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
×
1132
        nYSize == nRasterYSize)
×
1133
    {
1134
        bFetchAll = true;
×
1135
    }
1136
    else
1137
    {
1138
        if (nXOff >= m_nLastLoadSourcesXOff &&
×
1139
            nYOff >= m_nLastLoadSourcesYOff &&
×
1140
            nXOff + nXSize <=
×
1141
                m_nLastLoadSourcesXOff + m_nLastLoadSourcesXSize &&
×
1142
            nYOff + nYSize <=
×
1143
                m_nLastLoadSourcesYOff + m_nLastLoadSourcesYSize &&
×
1144
            nBand == m_nLastLoadSourcesBand)
×
1145
        {
1146
            return true;
×
1147
        }
1148

1149
        // To avoid doing too many small requests, try to query for at
1150
        // least 10 megapixels.
1151
        if (nXSize * nYSize < 10 * 1024 * 1024)
×
1152
        {
1153
            nYSizeToQuery = 10 * 1024 * 1024 / nXSize;
×
1154
            nYSizeToQuery = std::min(nYSizeToQuery, nRasterYSize - nYOff);
×
1155
            if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
×
1156
                nYSize == nRasterYSize)
×
1157
            {
1158
                bFetchAll = true;
×
1159
            }
1160
        }
1161
    }
1162

1163
    if (!bFetchAll)
×
1164
    {
1165
        double adfProjWin[8];
1166
        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
                        adfProjWin[0], adfProjWin[1]*/);
×
1183
    }
1184

1185
    bool bLoadRasters =
1186
        CPLTestBool(CPLGetConfigOption("PR_FORCE_LOAD_RASTERS", "FALSE"));
×
1187
    bool bAllBandCaching = false;
×
1188

1189
    const std::string osPrimaryKeyNameI(
1190
        CPLQuotedSQLIdentifier(pszPrimaryKeyName));
×
1191
    const std::string osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
×
1192
    const std::string osTableI(CPLQuotedSQLIdentifier(pszTable));
×
1193
    const std::string osColumnI(CPLQuotedSQLIdentifier(pszColumn));
×
1194

1195
    PGresult *poResult = nullptr;
×
1196
    if (m_nTiles > 0 && !bFetchAll)
×
1197
    {
1198
        CPLString osCommand;
×
1199
        osCommand.Printf("SELECT %s FROM %s.%s", osPrimaryKeyNameI.c_str(),
1200
                         osSchemaI.c_str(), osTableI.c_str());
×
1201
        osCommand += " WHERE ";
×
1202
        osCommand += osSpatialFilter;
×
1203

1204
        osSpatialFilter = "";
×
1205

1206
        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
        if (poResult == nullptr ||
×
1216
            PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
1217
            PQntuples(poResult) < 0)
×
1218
        {
1219

1220
            if (poResult)
×
1221
                PQclear(poResult);
×
1222

1223
            CPLError(CE_Failure, CPLE_AppDefined,
×
1224
                     "PostGISRasterDataset::LoadSources(): %s",
1225
                     PQerrorMessage(poConn));
×
1226

1227
            return false;
×
1228
        }
1229

1230
        if (bTilesSameDimension && nBand > 0)
×
1231
        {
1232
            const GIntBig nMemoryRequiredForTiles =
1233
                static_cast<GIntBig>(PQntuples(poResult)) * nTileWidth *
×
1234
                nTileHeight *
×
1235
                GDALGetDataTypeSizeBytes(
×
1236
                    GetRasterBand(nBand)->GetRasterDataType());
×
1237
            const GIntBig nCacheMax = GDALGetCacheMax64();
×
1238
            if (nBands * nMemoryRequiredForTiles <= nCacheMax)
×
1239
            {
1240
                bLoadRasters = true;
×
1241
                bAllBandCaching = true;
×
1242
            }
1243
            else if (nMemoryRequiredForTiles <= nCacheMax)
×
1244
            {
1245
                bLoadRasters = true;
×
1246
            }
1247
        }
1248

1249
        int i;
1250
        for (i = 0; i < PQntuples(poResult); i++)
×
1251
        {
1252
            const char *pszPKID = PQgetvalue(poResult, i, 0);
×
1253
            PostGISRasterTileDataset *poTile = GetMatchingSourceRef(pszPKID);
×
1254
            int bFetchTile = FALSE;
×
1255
            if (poTile == nullptr)
×
1256
                bFetchTile = TRUE;
×
1257
            else if (bLoadRasters)
×
1258
            {
1259
                PostGISRasterTileRasterBand *poTileBand =
1260
                    cpl::down_cast<PostGISRasterTileRasterBand *>(
×
1261
                        poTile->GetRasterBand(nBand));
1262
                if (!poTileBand->IsCached())
×
1263
                    bFetchTile = TRUE;
×
1264
            }
1265
            if (bFetchTile)
×
1266
            {
1267
                if (!osIDsToFetch.empty())
×
1268
                    osIDsToFetch += ",";
×
1269
                osIDsToFetch += "'";
×
1270
                osIDsToFetch += pszPKID;
×
1271
                osIDsToFetch += "'";
×
1272
            }
1273
        }
1274

1275
        PQclear(poResult);
×
1276
    }
1277

1278
    if (bFetchAll || !osIDsToFetch.empty() || !osSpatialFilter.empty())
×
1279
    {
1280
        std::string osWHERE;
×
1281
        if (!osIDsToFetch.empty())
×
1282
        {
1283
            osWHERE += osPrimaryKeyNameI;
×
1284
            osWHERE += " IN (";
×
1285
            osWHERE += osIDsToFetch;
×
1286
            osWHERE += ")";
×
1287
        }
1288
        else if (!osSpatialFilter.empty())
×
1289
        {
1290
            osWHERE = std::move(osSpatialFilter);
×
1291
        }
1292
        if (pszWhere != nullptr)
×
1293
        {
1294
            if (!osWHERE.empty())
×
1295
                osWHERE += " AND ";
×
1296
            osWHERE += "(";
×
1297
            osWHERE += pszWhere;
×
1298
            osWHERE += ")";
×
1299
        }
1300

1301
        bool bCanUseClientSide = true;
×
1302
        if (bLoadRasters &&
×
1303
            eOutDBResolution == OutDBResolution::CLIENT_SIDE_IF_POSSIBLE)
×
1304
        {
1305
            bCanUseClientSide =
1306
                CanUseClientSideOutDB(bAllBandCaching, nBand, osWHERE);
×
1307
        }
1308

1309
        CPLString osCommand;
×
1310
        osCommand.Printf("SELECT %s, ST_Metadata(%s)",
1311
                         osPrimaryKeyNameI.c_str(), osColumnI.c_str());
×
1312
        if (bLoadRasters)
×
1313
        {
1314
            CPLString orRasterToFetch;
×
1315
            if (bAllBandCaching)
×
1316
            {
1317
                orRasterToFetch = osColumnI;
×
1318
            }
1319
            else
1320
            {
1321
                orRasterToFetch.Printf("ST_Band(%s, %d)", osColumnI.c_str(),
1322
                                       nBand);
×
1323
            }
1324
            if (eOutDBResolution == OutDBResolution::SERVER_SIDE ||
×
1325
                !bCanUseClientSide)
×
1326
            {
1327
                orRasterToFetch =
1328
                    "encode(ST_AsBinary(" + orRasterToFetch + ",TRUE),'hex')";
×
1329
            }
1330
            osCommand += ", " + orRasterToFetch;
×
1331
        }
1332
        osCommand +=
1333
            CPLSPrintf(" FROM %s.%s", osSchemaI.c_str(), osTableI.c_str());
×
1334
        if (!osWHERE.empty())
×
1335
        {
1336
            osCommand += " WHERE ";
×
1337
            osCommand += osWHERE;
×
1338
        }
1339

1340
        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
        if (poResult == nullptr ||
×
1350
            PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
1351
            PQntuples(poResult) < 0)
×
1352
        {
1353

1354
            if (poResult)
×
1355
                PQclear(poResult);
×
1356

1357
            CPLError(CE_Failure, CPLE_AppDefined,
×
1358
                     "PostGISRasterDataset::LoadSources(): %s",
1359
                     PQerrorMessage(poConn));
×
1360

1361
            return false;
×
1362
        }
1363

1364
        for (int i = 0; i < PQntuples(poResult); i++)
×
1365
        {
1366
            const char *pszPKID = PQgetvalue(poResult, i, 0);
×
1367
            const char *pszMetadata = PQgetvalue(poResult, i, 1);
×
1368

1369
            PostGISRasterTileDataset *poRTDS = GetMatchingSourceRef(pszPKID);
×
1370
            if (poRTDS == nullptr)
×
1371
            {
1372
                poRTDS = BuildRasterTileDataset(pszMetadata, pszPKID,
×
1373
                                                GetRasterCount(), nullptr);
1374
                if (poRTDS != nullptr)
×
1375
                {
1376
                    AddComplexSource(poRTDS);
×
1377

1378
                    oMapPKIDToRTDS[poRTDS->pszPKID] = poRTDS;
×
1379
                    papoSourcesHolders =
×
1380
                        static_cast<PostGISRasterTileDataset **>(
1381
                            CPLRealloc(papoSourcesHolders,
×
1382
                                       sizeof(PostGISRasterTileDataset *) *
1383
                                           (m_nTiles + 1)));
×
1384
                    papoSourcesHolders[m_nTiles++] = poRTDS;
×
1385
                    CPLQuadTreeInsert(hQuadTree, poRTDS);
×
1386
                }
1387
            }
1388

1389
            if (bLoadRasters && poRTDS != nullptr)
×
1390
            {
1391
                const char *pszRaster = PQgetvalue(poResult, i, 2);
×
1392
                CacheTile(pszMetadata, pszRaster, pszPKID, nBand,
×
1393
                          bAllBandCaching);
1394
            }
1395
        }
1396

1397
        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
    if (bFetchAll)
×
1404
        bBuildQuadTreeDynamically = false;
×
1405

1406
    m_nLastLoadSourcesXOff = nXOff;
×
1407
    m_nLastLoadSourcesYOff = nYOff;
×
1408
    m_nLastLoadSourcesXSize = nXSize;
×
1409
    m_nLastLoadSourcesYSize = nYSizeToQuery;
×
1410
    m_nLastLoadSourcesBand = nBand;
×
1411

1412
    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
bool PostGISRasterDataset::CanUseClientSideOutDB(bool bAllBandCaching,
×
1420
                                                 int nBand,
1421
                                                 const CPLString &osWHERE)
1422
{
1423
    CPLString osCommand;
×
1424
    CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
×
1425
    CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
×
1426
    CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
×
1427

1428
    if (bAllBandCaching)
×
1429
    {
1430
        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
                !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
                !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
×
1451
        }
1452
    }
1453
    else
1454
    {
1455
        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
                !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
                !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
×
1471
        }
1472
    }
1473
    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
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
1482
        PQntuples(poResult) < 0)
×
1483
    {
1484

1485
        if (poResult)
×
1486
            PQclear(poResult);
×
1487

1488
        CPLError(CE_Failure, CPLE_AppDefined,
×
1489
                 "PostGISRasterRasterBand::CanUseClientSideOutDB(): %s",
1490
                 PQerrorMessage(poConn));
×
1491

1492
        return false;
×
1493
    }
1494
    bool bCanUseClientSide = true;
×
1495
    const int nTuples = PQntuples(poResult);
×
1496
    for (int i = 0; i < nTuples; i++)
×
1497
    {
1498
        const char *pszFilename = PQgetvalue(poResult, i, 0);
×
1499
        if (pszFilename)
×
1500
        {
1501
            bool bUsable = false;
×
1502
            if (!oOutDBFilenameUsable.tryGet(std::string(pszFilename), bUsable))
×
1503
            {
1504
                VSIStatBufL sStat;
1505
                bUsable = (VSIStatL(pszFilename, &sStat) == 0);
×
1506
                if (bUsable && bHasStBandFileSize)
×
1507
                {
1508
                    const char *pszSize = PQgetvalue(poResult, i, 1);
×
1509
                    const char *pszTimestamp = PQgetvalue(poResult, i, 2);
×
1510
                    if (pszSize && pszTimestamp)
×
1511
                    {
1512
                        bUsable &=
×
1513
                            (static_cast<GUInt64>(CPLAtoGIntBig(pszSize)) ==
×
1514
                             static_cast<GUInt64>(sStat.st_size));
×
1515
                        bUsable &= (static_cast<GUInt64>(
×
1516
                                        CPLAtoGIntBig(pszTimestamp)) ==
×
1517
                                    static_cast<GUInt64>(sStat.st_mtime));
×
1518
                    }
1519
                }
1520
                oOutDBFilenameUsable.insert(std::string(pszFilename), bUsable);
×
1521
            }
1522
            if (!bUsable)
×
1523
            {
1524
                CPLDebug("PostGIS_Raster",
×
1525
                         "File %s not usable from client side", pszFilename);
1526
                bCanUseClientSide = false;
×
1527
            }
1528
        }
1529
    }
1530
    PQclear(poResult);
×
1531
    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
BandMetadata *PostGISRasterDataset::GetBandsMetadata(int *pnBands)
×
1540
{
1541
    BandMetadata *poBMD = nullptr;
×
1542
    PGresult *poResult = nullptr;
×
1543
    CPLString osCommand;
×
1544
    char *pszRes = nullptr;
×
1545
    char *pszFilteredRes = nullptr;
×
1546
    char **papszParams = nullptr;
×
1547

1548
    CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
×
1549
    CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
×
1550
    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
                     pszWhere ? pszWhere : "true", osColumnI.c_str(),
×
1559
                     nBandsToCreate);
×
1560

1561
#ifdef DEBUG_QUERY
1562
    CPLDebug("PostGIS_Raster",
1563
             "PostGISRasterDataset::GetBandsMetadata(): Query: %s",
1564
             osCommand.c_str());
1565
#endif
1566

1567
    poResult = PQexec(poConn, osCommand.c_str());
×
1568
    /* Error getting info from database */
1569
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
1570
        PQntuples(poResult) <= 0)
×
1571
    {
1572

1573
        ReportError(CE_Failure, CPLE_AppDefined,
×
1574
                    "Error getting band metadata while creating raster "
1575
                    "bands");
1576

1577
        CPLDebug("PostGIS_Raster",
×
1578
                 "PostGISRasterDataset::GetBandsMetadata(): %s",
1579
                 PQerrorMessage(poConn));
×
1580

1581
        if (poResult)
×
1582
            PQclear(poResult);
×
1583

1584
        return nullptr;
×
1585
    }
1586

1587
    // Matches nBands
1588
    int nTuples = PQntuples(poResult);
×
1589

1590
    poBMD = static_cast<BandMetadata *>(
1591
        VSI_MALLOC2_VERBOSE(nTuples, sizeof(BandMetadata)));
×
1592
    if (poBMD == nullptr)
×
1593
    {
1594
        PQclear(poResult);
×
1595

1596
        return nullptr;
×
1597
    }
1598

1599
    int iBand = 0;
×
1600

1601
    for (iBand = 0; iBand < nTuples; iBand++)
×
1602
    {
1603

1604
        // Get metadata record
1605
        pszRes = CPLStrdup(PQgetvalue(poResult, iBand, 0));
×
1606

1607
        // Skip first "("
1608
        pszFilteredRes = pszRes + 1;
×
1609

1610
        // Skip last ")"
1611
        pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
×
1612

1613
        // Tokenize
1614
        papszParams = CSLTokenizeString2(
×
1615
            pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
1616
        CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_BAND_METADATA_RECORD);
×
1617

1618
        CPLFree(pszRes);
×
1619

1620
        // If the band doesn't have nodata, NULL is returned as nodata
1621
        TranslateDataType(papszParams[POS_PIXELTYPE], &(poBMD[iBand].eDataType),
×
1622
                          &(poBMD[iBand].nBitsDepth));
×
1623

1624
        if (papszParams[POS_NODATAVALUE] == nullptr ||
×
1625
            EQUAL(papszParams[POS_NODATAVALUE], "NULL") ||
×
1626
            EQUAL(papszParams[POS_NODATAVALUE], "f") ||
×
1627
            EQUAL(papszParams[POS_NODATAVALUE], ""))
×
1628
        {
1629

1630
            poBMD[iBand].bHasNoDataValue = false;
×
1631
            poBMD[iBand].dfNoDataValue = CPLAtof(NO_VALID_RES);
×
1632
        }
1633

1634
        else
1635
        {
1636
            poBMD[iBand].bHasNoDataValue = true;
×
1637
            poBMD[iBand].dfNoDataValue = CPLAtof(papszParams[POS_NODATAVALUE]);
×
1638
        }
1639

1640
        poBMD[iBand].bIsOffline = (papszParams[POS_ISOUTDB] != nullptr)
×
1641
                                      ? EQUAL(papszParams[POS_ISOUTDB], "t")
×
1642
                                      : false;
1643

1644
        CSLDestroy(papszParams);
×
1645
    }
1646

1647
    if (pnBands)
×
1648
        *pnBands = nTuples;
×
1649

1650
    PQclear(poResult);
×
1651

1652
    return poBMD;
×
1653
}
1654

1655
/***********************************************************************
1656
 * \brief Function to get the bounding box of each element inserted in
1657
 * the QuadTree index
1658
 **********************************************************************/
1659
static void GetTileBoundingBox(const void *hFeature, CPLRectObj *pBounds)
×
1660
{
1661
    PostGISRasterTileDataset *poRTD = const_cast<PostGISRasterTileDataset *>(
×
1662
        reinterpret_cast<const PostGISRasterTileDataset *>(hFeature));
1663

1664
    GDALGeoTransform tileGT;
×
1665
    poRTD->GetGeoTransform(tileGT);
×
1666

1667
    int nTileWidth = poRTD->GetRasterXSize();
×
1668
    int nTileHeight = poRTD->GetRasterYSize();
×
1669

1670
    pBounds->minx = tileGT[GEOTRSFRM_TOPLEFT_X];
×
1671
    pBounds->maxx =
×
1672
        tileGT[GEOTRSFRM_TOPLEFT_X] + nTileWidth * tileGT[GEOTRSFRM_WE_RES];
×
1673

1674
    if (tileGT[GEOTRSFRM_NS_RES] >= 0.0)
×
1675
    {
1676
        pBounds->miny = tileGT[GEOTRSFRM_TOPLEFT_Y];
×
1677
        pBounds->maxy = tileGT[GEOTRSFRM_TOPLEFT_Y] +
×
1678
                        nTileHeight * tileGT[GEOTRSFRM_NS_RES];
×
1679
    }
1680
    else
1681
    {
1682
        pBounds->maxy = tileGT[GEOTRSFRM_TOPLEFT_Y];
×
1683
        pBounds->miny = tileGT[GEOTRSFRM_TOPLEFT_Y] +
×
1684
                        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
    return;
×
1696
}
1697

1698
/********************************************************
1699
 * \brief Builds a PostGISRasterTileDataset* object from the ST_Metadata
1700
 ********************************************************/
1701
PostGISRasterTileDataset *PostGISRasterDataset::BuildRasterTileDataset(
×
1702
    const char *pszMetadata, const char *pszPKID, int nBandsFetched,
1703
    BandMetadata *poBandMetaData)
1704
{
1705
    // Get metadata record
1706
    char *pszRes = CPLStrdup(pszMetadata);
×
1707

1708
    // Skip first "("
1709
    char *pszFilteredRes = pszRes + 1;
×
1710

1711
    // Skip last ")"
1712
    pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
×
1713

1714
    // Tokenize
1715
    char **papszParams = CSLTokenizeString2(
×
1716
        pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
1717
    CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_METADATA_RECORD);
×
1718

1719
    CPLFree(pszRes);
×
1720

1721
    double tileSkewX = CPLAtof(papszParams[POS_SKEWX]);
×
1722
    double tileSkewY = CPLAtof(papszParams[POS_SKEWY]);
×
1723

1724
    // Rotated rasters are not allowed, so far
1725
    // TODO: allow them
1726
    if (!CPLIsEqual(tileSkewX, 0.0) || !CPLIsEqual(tileSkewY, 0.0))
×
1727
    {
1728

1729
        ReportError(CE_Failure, CPLE_AppDefined,
×
1730
                    "GDAL PostGIS Raster driver can not work with "
1731
                    "rotated rasters yet.");
1732

1733
        CSLDestroy(papszParams);
×
1734
        return nullptr;
×
1735
    }
1736

1737
    int l_nTileWidth = atoi(papszParams[POS_WIDTH]);
×
1738
    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
    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
    if (nTileBands != nBandsFetched)
×
1751
    {
1752
        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
        CSLDestroy(papszParams);
×
1760

1761
        return nullptr;
×
1762
    }
1763

1764
    PostGISRasterTileDataset *poRTDS =
1765
        new PostGISRasterTileDataset(this, l_nTileWidth, l_nTileHeight);
×
1766
    poRTDS->ShareLockWithParentDataset(this);
×
1767

1768
    if (GetPrimaryKeyRef() != nullptr)
×
1769
    {
1770
        poRTDS->pszPKID = CPLStrdup(pszPKID);
×
1771
    }
1772

1773
    poRTDS->m_gt[GEOTRSFRM_TOPLEFT_X] = CPLAtof(papszParams[POS_UPPERLEFTX]);
×
1774

1775
    poRTDS->m_gt[GEOTRSFRM_TOPLEFT_Y] = CPLAtof(papszParams[POS_UPPERLEFTY]);
×
1776

1777
    poRTDS->m_gt[GEOTRSFRM_WE_RES] = CPLAtof(papszParams[POS_SCALEX]);
×
1778

1779
    poRTDS->m_gt[GEOTRSFRM_NS_RES] = CPLAtof(papszParams[POS_SCALEY]);
×
1780

1781
    for (int j = 0; j < nTileBands; j++)
×
1782
    {
1783

1784
        // Create band
1785
        poRTDS->SetBand(j + 1,
×
1786
                        new PostGISRasterTileRasterBand(
1787
                            poRTDS, j + 1,
1788
                            (poBandMetaData)
1789
                                ? poBandMetaData[j].eDataType
×
1790
                                : GetRasterBand(j + 1)->GetRasterDataType()));
×
1791
    }
1792

1793
    CSLDestroy(papszParams);
×
1794

1795
    return poRTDS;
×
1796
}
1797

1798
/********************************************************
1799
 * \brief Updates components GEOTRSFRM_WE_RES and GEOTRSFRM_NS_RES
1800
 *        of dataset m_gt
1801
 ********************************************************/
1802
void PostGISRasterDataset::UpdateGlobalResolutionWithTileResolution(
×
1803
    double tilePixelSizeX, double tilePixelSizeY)
1804
{
1805
    // Calculate pixel size
1806
    if (resolutionStrategy == AVERAGE_RESOLUTION ||
×
1807
        resolutionStrategy == AVERAGE_APPROX_RESOLUTION)
×
1808
    {
1809
        m_gt[GEOTRSFRM_WE_RES] += tilePixelSizeX;
×
1810
        m_gt[GEOTRSFRM_NS_RES] += tilePixelSizeY;
×
1811
    }
1812

1813
    else if (resolutionStrategy == HIGHEST_RESOLUTION)
×
1814
    {
1815
        m_gt[GEOTRSFRM_WE_RES] =
×
1816
            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
        if (tilePixelSizeY < 0.0)
×
1832
            m_gt[GEOTRSFRM_NS_RES] =
×
1833
                std::max(m_gt[GEOTRSFRM_NS_RES], tilePixelSizeY);
×
1834
        else
1835
            m_gt[GEOTRSFRM_NS_RES] =
×
1836
                std::min(m_gt[GEOTRSFRM_NS_RES], tilePixelSizeY);
×
1837
    }
1838

1839
    else if (resolutionStrategy == LOWEST_RESOLUTION)
×
1840
    {
1841
        m_gt[GEOTRSFRM_WE_RES] =
×
1842
            std::max(m_gt[GEOTRSFRM_WE_RES], tilePixelSizeX);
×
1843

1844
        if (tilePixelSizeY < 0.0)
×
1845
            m_gt[GEOTRSFRM_NS_RES] =
×
1846
                std::min(m_gt[GEOTRSFRM_NS_RES], tilePixelSizeY);
×
1847
        else
1848
            m_gt[GEOTRSFRM_NS_RES] =
×
1849
                std::max(m_gt[GEOTRSFRM_NS_RES], tilePixelSizeY);
×
1850
    }
1851
}
×
1852

1853
/***********************************************************************
1854
 * \brief Build bands
1855
 ***********************************************************************/
1856
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
    for (iBand = 0; iBand < nBandsFetched; iBand++)
×
1867
    {
1868

1869
        SetBand(iBand + 1, new PostGISRasterRasterBand(
×
1870
                               this, iBand + 1, poBandMetaData[iBand].eDataType,
×
1871
                               poBandMetaData[iBand].bHasNoDataValue,
×
1872
                               poBandMetaData[iBand].dfNoDataValue));
×
1873

1874
        // Set some band metadata items
1875
        GDALRasterBand *b = GetRasterBand(iBand + 1);
×
1876
        if (poBandMetaData[iBand].nBitsDepth < 8)
×
1877
        {
1878
            b->SetMetadataItem(
×
1879
                "NBITS",
1880
                CPLString().Printf("%d", poBandMetaData[iBand].nBitsDepth),
×
1881
                "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
}
×
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
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
    int nBandsFetched = 0;
×
1920
    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
    int l_nTiles = PQntuples(poResult);
×
1931

1932
    m_gt[GEOTRSFRM_TOPLEFT_X] = xmin;
×
1933

1934
    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
    papoSourcesHolders = static_cast<PostGISRasterTileDataset **>(
×
1952
        VSI_CALLOC_VERBOSE(l_nTiles, sizeof(PostGISRasterTileDataset *)));
×
1953

1954
    if (papoSourcesHolders == nullptr)
×
1955
    {
1956
        VSIFree(poBandMetaData);
×
1957

1958
        return false;
×
1959
    }
1960

1961
    int nValidTiles = 0;
×
1962
    for (i = 0; i < l_nTiles; i++)
×
1963
    {
1964
        PostGISRasterTileDataset *poRTDS = BuildRasterTileDataset(
×
1965
            PQgetvalue(poResult, i, nField),
×
1966
            (GetPrimaryKeyRef() != nullptr) ? PQgetvalue(poResult, i, 0)
×
1967
                                            : nullptr,
1968
            nBandsFetched, poBandMetaData);
1969
        if (poRTDS == nullptr)
×
1970
            continue;
×
1971

1972
        if (nOverviewFactor == 1 && resolutionStrategy != USER_RESOLUTION)
×
1973
        {
1974
            double tilePixelSizeX = poRTDS->m_gt[GEOTRSFRM_WE_RES];
×
1975
            double tilePixelSizeY = poRTDS->m_gt[GEOTRSFRM_NS_RES];
×
1976

1977
            if (nValidTiles == 0)
×
1978
            {
1979
                m_gt[GEOTRSFRM_WE_RES] = tilePixelSizeX;
×
1980
                m_gt[GEOTRSFRM_NS_RES] = tilePixelSizeY;
×
1981
            }
1982
            else
1983
            {
1984
                UpdateGlobalResolutionWithTileResolution(tilePixelSizeX,
×
1985
                                                         tilePixelSizeY);
1986
            }
1987
        }
1988

1989
        papoSourcesHolders[nValidTiles++] = poRTDS;
×
1990
    }  // end for
1991

1992
    l_nTiles = nValidTiles;
×
1993

1994
    if (nOverviewFactor > 1)
×
1995
    {
1996
        m_gt[GEOTRSFRM_WE_RES] =
×
1997
            poParentDS->m_gt[GEOTRSFRM_WE_RES] * nOverviewFactor;
×
1998
        m_gt[GEOTRSFRM_NS_RES] =
×
1999
            poParentDS->m_gt[GEOTRSFRM_NS_RES] * nOverviewFactor;
×
2000
    }
2001
    else if ((resolutionStrategy == AVERAGE_RESOLUTION ||
×
2002
              resolutionStrategy == AVERAGE_APPROX_RESOLUTION) &&
×
2003
             l_nTiles > 0)
2004
    {
2005
        m_gt[GEOTRSFRM_WE_RES] /= l_nTiles;
×
2006
        m_gt[GEOTRSFRM_NS_RES] /= l_nTiles;
×
2007
    }
2008

2009
    /**
2010
     * Complete the rest of geotransform parameters
2011
     **/
2012
    if (m_gt[GEOTRSFRM_NS_RES] >= 0.0)
×
2013
        m_gt[GEOTRSFRM_TOPLEFT_Y] = ymin;
×
2014
    else
2015
        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
    nRasterXSize =
×
2028
        static_cast<int>(fabs(rint((xmax - xmin) / m_gt[GEOTRSFRM_WE_RES])));
×
2029

2030
    nRasterYSize =
×
2031
        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
    if (nRasterXSize <= 0 || nRasterYSize <= 0)
×
2041
    {
2042
        ReportError(CE_Failure, CPLE_AppDefined,
×
2043
                    "Computed PostGIS Raster dimension is invalid. You've "
2044
                    "probably specified inappropriate resolution.");
2045

2046
        VSIFree(poBandMetaData);
×
2047
        return false;
×
2048
    }
2049

2050
    /*******************************************************************
2051
     * Now construct the dataset bands
2052
     ******************************************************************/
2053
    BuildBands(poBandMetaData, nBandsFetched);
×
2054

2055
    // And free bandmetadata
2056
    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
    for (int iSource = 0; iSource < l_nTiles; iSource++)
×
2067
    {
2068
        PostGISRasterTileDataset *poRTDS = papoSourcesHolders[iSource];
×
2069
        AddComplexSource(poRTDS);
×
2070
        if (poRTDS->pszPKID != nullptr)
×
2071
            oMapPKIDToRTDS[poRTDS->pszPKID] = poRTDS;
×
2072
        CPLQuadTreeInsert(hQuadTree, poRTDS);
×
2073
    }
2074

2075
    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
GBool PostGISRasterDataset::YieldSubdatasets(
×
2091
    PGresult *poResult, const char *pszValidConnectionString)
2092
{
2093
    int l_nTiles = PQntuples(poResult);
×
2094
    int i = 0;
×
2095

2096
    papszSubdatasets =
×
2097
        static_cast<char **>(VSICalloc(2 * l_nTiles + 1, sizeof(char *)));
×
2098
    if (papszSubdatasets == nullptr)
×
2099
        return false;
×
2100

2101
    CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
×
2102

2103
    // Subdatasets identified by primary key
2104
    if (GetPrimaryKeyRef() != nullptr)
×
2105
    {
2106
        CPLString osPrimaryKeyNameI(CPLQuotedSQLIdentifier(pszPrimaryKeyName));
×
2107

2108
        for (i = 0; i < l_nTiles; i++)
×
2109
        {
2110

2111
            const char *pszId = PQgetvalue(poResult, i, 0);
×
2112

2113
            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
            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
        for (i = 0; i < l_nTiles; i++)
×
2130
        {
2131
            char *pszRes = CPLStrdup(PQgetvalue(poResult, i, 0));
×
2132

2133
            // Skip first "("
2134
            char *pszFilteredRes = pszRes + 1;
×
2135

2136
            // Skip last ")"
2137
            pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
×
2138

2139
            // Tokenize
2140
            char **papszParams =
2141
                CSLTokenizeString2(pszFilteredRes, ",", CSLT_HONOURSTRINGS);
×
2142

2143
            CPLFree(pszRes);
×
2144

2145
            const double dfTileUpperLeftX =
2146
                CPLAtof(papszParams[POS_UPPERLEFTX]);
×
2147
            const double dfTileUpperLeftY =
2148
                CPLAtof(papszParams[POS_UPPERLEFTY]);
×
2149

2150
            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
            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
            CSLDestroy(papszParams);
×
2165
        }
2166
    }
2167

2168
    /**
2169
     * Not a single raster fetched. Not really needed. Just to keep code clean
2170
     **/
2171
    nRasterXSize = 0;
×
2172
    nRasterYSize = 0;
×
2173
    m_gt[GEOTRSFRM_TOPLEFT_X] = 0.0;
×
2174
    m_gt[GEOTRSFRM_WE_RES] = 1.0;
×
2175
    m_gt[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
×
2176
    m_gt[GEOTRSFRM_TOPLEFT_Y] = 0.0;
×
2177
    m_gt[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
×
2178
    m_gt[GEOTRSFRM_NS_RES] = -1.0;
×
2179

2180
    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
GBool PostGISRasterDataset::SetRasterProperties(
2✔
2206
    const char *pszValidConnectionString)
2207
{
2208
    PGresult *poResult = nullptr;
2✔
2209
    GBool bDataFoundInRasterColumns = false;
2✔
2210
    GBool bNeedToCheckWholeTable = false;
2✔
2211

2212
    CPLString osCommand;
4✔
2213
    CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
4✔
2214
    CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
4✔
2215
    CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
4✔
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
    if (pszWhere != nullptr)
2✔
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
            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
        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
            pszSchema, pszTable, pszColumn);
2✔
2269

2270
#ifdef DEBUG_QUERY
2271
        CPLDebug("PostGIS_Raster",
2272
                 "PostGISRasterDataset::SetRasterProperties(): First query: %s",
2273
                 osCommand.c_str());
2274
#endif
2275

2276
        poResult = PQexec(poConn, osCommand.c_str());
2✔
2277

2278
        // Query execution error
2279
        if (poResult == nullptr ||
4✔
2280
            PQresultStatus(poResult) != PGRES_TUPLES_OK ||
4✔
2281
            PQntuples(poResult) < 0)
2✔
2282
        {
2283
            bNeedToCheckWholeTable = true;
×
2284

2285
            if (poResult)
×
2286
                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
        else if (PQntuples(poResult) == 0)
2✔
2294
        {
2295

2296
            ReportError(
2✔
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
            PQclear(poResult);
2✔
2307

2308
            bNeedToCheckWholeTable = true;
2✔
2309
        }
2310

2311
        /* There's a result but the row has empty values */
2312
        else if (PQntuples(poResult) == 1 &&
×
2313
                 (PQgetvalue(poResult, 0, 1)[0] == '\0' ||
×
2314
                  (poParentDS == nullptr &&
×
2315
                   (PQgetvalue(poResult, 0, 2)[0] == '\0' ||
×
2316
                    PQgetvalue(poResult, 0, 3)[0] == '\0' ||
×
2317
                    PQgetvalue(poResult, 0, 4)[0] == '\0' ||
×
2318
                    PQgetvalue(poResult, 0, 5)[0] == '\0'))))
×
2319
        {
2320
            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
            PQclear(poResult);
×
2331

2332
            bNeedToCheckWholeTable = true;
×
2333
        }
2334

2335
        // We should check whole table but we can't
2336
        if (bNeedToCheckWholeTable && !bCheckAllTiles)
2✔
2337
        {
2338
            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
            PQclear(poResult);
×
2353

2354
            return false;
×
2355
        }
2356

2357
        // We should check the whole table and we can
2358
        else if (bNeedToCheckWholeTable)
2✔
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
                osTableI.c_str(), osColumnI.c_str());
2✔
2371

2372
#ifdef DEBUG_QUERY
2373
            CPLDebug("PostGIS_Raster",
2374
                     "PostGISRasterDataset::SetRasterProperties(): "
2375
                     "First query: %s",
2376
                     osCommand.c_str());
2377
#endif
2378

2379
            poResult = PQexec(poConn, osCommand.c_str());
2✔
2380
        }
2381

2382
        // We already found the data in raster_columns
2383
        else
2384
        {
2385
            bDataFoundInRasterColumns = true;
×
2386
        }
2387
    }
2388

2389
    // Query execution error
2390
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
2✔
2391
        PQntuples(poResult) < 0)
×
2392
    {
2393

2394
        ReportError(CE_Failure, CPLE_AppDefined,
2✔
2395
                    "Error browsing database for PostGIS Raster "
2396
                    "properties : %s",
2397
                    PQerrorMessage(poConn));
2✔
2398

2399
        if (poResult != nullptr)
2✔
2400
            PQclear(poResult);
2✔
2401

2402
        return false;
2✔
2403
    }
2404

2405
    else if (PQntuples(poResult) == 0)
×
2406
    {
2407
        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
        PQclear(poResult);
×
2413

2414
        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
    else if (PQntuples(poResult) > 1)
×
2424
    {
2425
        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
        PQclear(poResult);
×
2434

2435
        return false;
×
2436
    }
2437

2438
    // Get some information we will probably need further
2439
    nSrid = atoi(PQgetvalue(poResult, 0, 0));
×
2440
    nBandsToCreate = atoi(PQgetvalue(poResult, 0, 1));
×
2441
    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
        xmin = poParentDS->xmin;
×
2446
        xmax = poParentDS->xmax;
×
2447
        ymin = poParentDS->ymin;
×
2448
        ymax = poParentDS->ymax;
×
2449
    }
2450
    else
2451
    {
2452
        xmin = CPLAtof(PQgetvalue(poResult, 0, 2));
×
2453
        xmax = CPLAtof(PQgetvalue(poResult, 0, 3));
×
2454
        ymin = CPLAtof(PQgetvalue(poResult, 0, 4));
×
2455
        ymax = CPLAtof(PQgetvalue(poResult, 0, 5));
×
2456
    }
2457

2458
    // Create the QuadTree object
2459
    CPLRectObj sRect;
2460
    sRect.minx = xmin;
×
2461
    sRect.miny = ymin;
×
2462
    sRect.maxx = xmax;
×
2463
    sRect.maxy = ymax;
×
2464
    hQuadTree = CPLQuadTreeCreate(&sRect, GetTileBoundingBox);
×
2465

2466
    double scale_x = CPLAtof(PQgetvalue(poResult, 0, 6));
×
2467
    double scale_y = CPLAtof(PQgetvalue(poResult, 0, 7));
×
2468
    if (nOverviewFactor > 1 && poParentDS != nullptr)
×
2469
    {
2470
        scale_x = poParentDS->m_gt[GEOTRSFRM_WE_RES] * nOverviewFactor;
×
2471
        scale_y = poParentDS->m_gt[GEOTRSFRM_NS_RES] * nOverviewFactor;
×
2472
    }
2473
    else if (resolutionStrategy == USER_RESOLUTION)
×
2474
    {
2475
        scale_x = m_gt[GEOTRSFRM_WE_RES];
×
2476
        scale_y = m_gt[GEOTRSFRM_NS_RES];
×
2477
    }
2478

2479
    // These fields can only be fetched from raster_columns view
2480
    if (bDataFoundInRasterColumns)
×
2481
    {
2482
        nTileWidth = atoi(PQgetvalue(poResult, 0, 8));
×
2483
        nTileHeight = atoi(PQgetvalue(poResult, 0, 9));
×
2484
        if (nTileWidth != 0 && nTileHeight != 0)
×
2485
            bTilesSameDimension = true;
×
2486

2487
        bAllTilesSnapToSameGrid = EQUAL(PQgetvalue(poResult, 0, 10), "t");
×
2488

2489
        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
    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
    if (GetPrimaryKeyRef() != nullptr)
×
2511
    {
2512
        CPLString osPrimaryKeyNameI(CPLQuotedSQLIdentifier(pszPrimaryKeyName));
×
2513

2514
        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
            if (bIsFastPK && nMode == ONE_RASTER_PER_TABLE &&
×
2520
                HasSpatialIndex() && (scale_x == 0 || scale_y == 0) &&
×
2521
                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
                                 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
                poResult = PQexec(poConn, osCommand.c_str());
×
2537
                if (poResult == nullptr ||
×
2538
                    PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
2539
                    PQntuples(poResult) <= 0)
×
2540
                {
2541

2542
                    ReportError(CE_Failure, CPLE_AppDefined,
×
2543
                                "Error retrieving raster metadata");
2544

2545
                    CPLDebug("PostGIS_Raster",
×
2546
                             "PostGISRasterDataset::SetRasterProperties(): %s",
2547
                             PQerrorMessage(poConn));
×
2548

2549
                    if (poResult != nullptr)
×
2550
                        PQclear(poResult);
×
2551

2552
                    return false;
×
2553
                }
2554

2555
                scale_x = CPLAtof(PQgetvalue(poResult, 0, 0));
×
2556
                scale_y = CPLAtof(PQgetvalue(poResult, 0, 1));
×
2557
                CPLDebug("PostGIS_Raster",
×
2558
                         "PostGISRasterDataset::SetRasterProperties: guessed: "
2559
                         "scale_x = %f, scale_y = %f",
2560
                         scale_x, scale_y);
2561

2562
                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
            if (bIsFastPK && nMode == ONE_RASTER_PER_TABLE &&
×
2571
                HasSpatialIndex() && scale_x != 0 && scale_y != 0)
×
2572
            {
2573
                m_gt[GEOTRSFRM_TOPLEFT_X] = xmin;
×
2574
                m_gt[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
×
2575
                m_gt[GEOTRSFRM_TOPLEFT_Y] = (scale_y < 0) ? ymax : ymin;
×
2576
                m_gt[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
×
2577
                m_gt[GEOTRSFRM_WE_RES] = scale_x;
×
2578
                m_gt[GEOTRSFRM_NS_RES] = scale_y;
×
2579

2580
                // Calculate the raster size from the geotransform array
2581
                nRasterXSize = static_cast<int>(
×
2582
                    fabs(rint((xmax - xmin) / m_gt[GEOTRSFRM_WE_RES])));
×
2583

2584
                nRasterYSize = static_cast<int>(
×
2585
                    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
                if (nRasterXSize <= 0 || nRasterYSize <= 0)
×
2595
                {
2596
                    ReportError(
×
2597
                        CE_Failure, CPLE_AppDefined,
2598
                        "Computed PostGIS Raster dimension is invalid. You "
2599
                        "have probably specified an inappropriate "
2600
                        "resolution.");
2601

2602
                    return false;
×
2603
                }
2604

2605
                bBuildQuadTreeDynamically = true;
×
2606

2607
                /**************************************************************
2608
                 * Now construct the dataset bands
2609
                 ***************************************************************/
2610
                int nBandsFetched = 0;
×
2611
                BandMetadata *poBandMetaData = GetBandsMetadata(&nBandsFetched);
×
2612

2613
                BuildBands(poBandMetaData, nBandsFetched);
×
2614

2615
                // And free bandmetadata
2616
                VSIFree(poBandMetaData);
×
2617

2618
                return true;
×
2619
            }
2620

2621
            osCommand.Printf("select %s, st_metadata(%s) from %s.%s",
2622
                             osPrimaryKeyNameI.c_str(), osColumnI.c_str(),
2623
                             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
                             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
        if (pszWhere == nullptr)
×
2641
        {
2642
            osCommand.Printf("select st_metadata(%s) from %s.%s",
2643
                             osColumnI.c_str(), osSchemaI.c_str(),
2644
                             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
                             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
    poResult = PQexec(poConn, osCommand.c_str());
×
2662
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
×
2663
        PQntuples(poResult) <= 0)
×
2664
    {
2665

2666
        ReportError(CE_Failure, CPLE_AppDefined,
×
2667
                    "Error retrieving raster metadata");
2668

2669
        CPLDebug("PostGIS_Raster",
×
2670
                 "PostGISRasterDataset::SetRasterProperties(): %s",
2671
                 PQerrorMessage(poConn));
×
2672

2673
        if (poResult != nullptr)
×
2674
            PQclear(poResult);
×
2675

2676
        return false;
×
2677
    }
2678

2679
    // Now we know the number of tiles that form our dataset
2680
    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
    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
        GBool res = ConstructOneDatasetFromTiles(poResult);
×
2699

2700
        PQclear(poResult);
×
2701

2702
        return res;
×
2703
    }
2704

2705
    /***************************************************************
2706
     * One raster per row: collect subdatasets
2707
     **************************************************************/
2708
    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
        GBool res = YieldSubdatasets(poResult, pszValidConnectionString);
×
2718

2719
        PQclear(poResult);
×
2720

2721
        return res;
×
2722
    }
2723

2724
    /***************************************************************
2725
     * Wrong mode: error
2726
     **************************************************************/
2727
    else
2728
    {
2729
        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
        PQclear(poResult);
×
2737

2738
        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
GetConnectionInfo(const char *pszFilename, char **ppszConnectionString,
2✔
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
    int nPos = -1, sPos = -1, i;
2✔
2773
    char *pszTmp = nullptr;
2✔
2774
    char **papszParams = PostGISRasterParseConnectionString(pszFilename);
2✔
2775
    if (papszParams == nullptr)
2✔
2776
    {
2777
        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
    nPos = CSLFindName(papszParams, "mode");
2✔
2788
    if (nPos != -1)
2✔
2789
    {
2790
        int tmp;
2791
        tmp = atoi(CPLParseNameValue(papszParams[nPos], nullptr));
×
2792

2793
        // default value
2794
        *nMode = ONE_RASTER_PER_ROW;
×
2795

2796
        if (tmp == 2)
×
2797
        {
2798
            *nMode = ONE_RASTER_PER_TABLE;
×
2799
        }
2800

2801
        /* Remove the mode from connection string */
2802
        papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
×
2803
    }
2804
    /* Default mode */
2805
    else
2806
        *nMode = ONE_RASTER_PER_ROW;
2✔
2807

2808
    nPos = CSLFindName(papszParams, "outdb_resolution");
2✔
2809
    *peOutDBResolution = OutDBResolution::SERVER_SIDE;
2✔
2810
    if (nPos != -1)
2✔
2811
    {
2812
        const char *pszValue = CPLParseNameValue(papszParams[nPos], nullptr);
×
2813
        if (EQUAL(pszValue, "server_side"))
×
2814
            *peOutDBResolution = OutDBResolution::SERVER_SIDE;
×
2815
        else if (EQUAL(pszValue, "client_side"))
×
2816
            *peOutDBResolution = OutDBResolution::CLIENT_SIDE;
×
2817
        else if (EQUAL(pszValue, "client_side_if_possible"))
×
2818
            *peOutDBResolution = OutDBResolution::CLIENT_SIDE_IF_POSSIBLE;
×
2819
        else
2820
        {
2821
            CPLError(CE_Failure, CPLE_NotSupported,
×
2822
                     "Unsupported value for outdb_resolution: %s", pszValue);
2823
        }
2824

2825
        /* Remove the mode from connection string */
2826
        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
    nPos = CSLFindName(papszParams, "dbname");
2✔
2834
    sPos = CSLFindName(papszParams, "service");
2✔
2835

2836
    if (nPos == -1 && sPos == -1)
2✔
2837
    {
2838
        CPLError(CE_Failure, CPLE_AppDefined,
×
2839
                 "You must specify at least a db name or a service name");
2840

2841
        CSLDestroy(papszParams);
×
2842

2843
        return false;
×
2844
    }
2845

2846
    *ppszDbname = (nPos != -1)
2✔
2847
                      ? CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr))
2✔
2848
                      : nullptr;
2849
    *ppszService =
2✔
2850
        (sPos != -1) ? CPLStrdup(CPLParseNameValue(papszParams[sPos], nullptr))
2✔
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
    nPos = CSLFindName(papszParams, "table");
2✔
2859
    if (nPos == -1)
2✔
2860
    {
2861
        *bBrowseDatabase = true;
×
2862

2863
        /* Get schema name, if exist */
2864
        nPos = CSLFindName(papszParams, "schema");
×
2865
        if (nPos != -1)
×
2866
        {
2867
            *ppszSchema =
×
2868
                CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
×
2869

2870
            /* Delete this pair from params array */
2871
            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
        nPos = CSLFindName(papszParams, "column");
×
2879
        if (nPos != -1)
×
2880
        {
2881
            /* Delete this pair from params array */
2882
            papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
×
2883
        }
2884

2885
        nPos = CSLFindName(papszParams, "where");
×
2886
        if (nPos != -1)
×
2887
        {
2888
            /* Delete this pair from params array */
2889
            papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
×
2890
        }
2891
    }
2892
    else
2893
    {
2894
        *bBrowseDatabase = false;
2✔
2895

2896
        *ppszTable = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
2✔
2897
        /* Delete this pair from params array */
2898
        papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
2✔
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
        nPos = CSLFindName(papszParams, "column");
2✔
2906
        if (nPos == -1)
2✔
2907
        {
2908
            *ppszColumn = CPLStrdup(DEFAULT_COLUMN);
2✔
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
            *ppszColumn =
×
2917
                CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
×
2918

2919
            /* Delete this pair from params array */
2920
            papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
×
2921
        }
2922

2923
        /* Get the rest of the parameters, if exist */
2924
        nPos = CSLFindName(papszParams, "schema");
2✔
2925
        if (nPos == -1)
2✔
2926
        {
2927
            *ppszSchema = CPLStrdup(DEFAULT_SCHEMA);
×
2928
        }
2929
        else
2930
        {
2931
            *ppszSchema =
2✔
2932
                CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
2✔
2933

2934
            /* Delete this pair from params array */
2935
            papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
2✔
2936
        }
2937

2938
        nPos = CSLFindName(papszParams, "where");
2✔
2939
        if (nPos != -1)
2✔
2940
        {
2941
            *ppszWhere =
×
2942
                CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
×
2943

2944
            /* Delete this pair from params array */
2945
            papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
×
2946
        }
2947
    }
2948

2949
    /* Parse ppszWhere, if needed */
2950
    if (*ppszWhere)
2✔
2951
    {
2952
        pszTmp =
2953
            ReplaceQuotes(*ppszWhere, static_cast<int>(strlen(*ppszWhere)));
×
2954
        CPLFree(*ppszWhere);
×
2955
        *ppszWhere = pszTmp;
×
2956
    }
2957

2958
    /***************************************
2959
     * Construct a valid connection string
2960
     ***************************************/
2961
    CPLString osConnectionString;
2✔
2962
    for (i = 0; i < CSLCount(papszParams); i++)
4✔
2963
    {
2964
        osConnectionString += papszParams[i];
2✔
2965
        osConnectionString += " ";
2✔
2966
    }
2967

2968
    /**********************************************************
2969
     * Set application name if not found in connection string
2970
     **********************************************************/
2971

2972
    if (*bBrowseDatabase == FALSE && *nMode == ONE_RASTER_PER_TABLE &&
2✔
2973
        CSLFindName(papszParams, "application_name") == -1 &&
4✔
2974
        getenv("PGAPPNAME") == nullptr)
×
2975
    {
2976
        osConnectionString += "application_name=";
×
2977
        osConnectionString += "'";
×
2978
        osConnectionString += "GDAL ";
×
2979
        osConnectionString += GDALVersionInfo("RELEASE_NAME");
×
2980
        osConnectionString += "'";
×
2981
        osConnectionString += " ";
×
2982
    }
2983

2984
    *ppszConnectionString = CPLStrdup(osConnectionString);
2✔
2985

2986
    nPos = CSLFindName(papszParams, "host");
2✔
2987
    if (nPos != -1)
2✔
2988
    {
2989
        *ppszHost = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
×
2990
    }
2991
    else if (CPLGetConfigOption("PGHOST", nullptr) != nullptr)
2✔
2992
    {
2993
        *ppszHost = CPLStrdup(CPLGetConfigOption("PGHOST", nullptr));
2✔
2994
    }
2995
    else
2996
        *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
    nPos = CSLFindName(papszParams, "port");
2✔
3008
    if (nPos != -1)
2✔
3009
    {
3010
        *ppszPort = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
×
3011
    }
3012
    else if (CPLGetConfigOption("PGPORT", nullptr) != nullptr)
2✔
3013
    {
3014
        *ppszPort = CPLStrdup(CPLGetConfigOption("PGPORT", nullptr));
2✔
3015
    }
3016
    else
3017
        *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
    nPos = CSLFindName(papszParams, "user");
2✔
3029
    if (nPos != -1)
2✔
3030
    {
3031
        *ppszUser = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
×
3032
    }
3033
    else if (CPLGetConfigOption("PGUSER", nullptr) != nullptr)
2✔
3034
    {
3035
        *ppszUser = CPLStrdup(CPLGetConfigOption("PGUSER", nullptr));
2✔
3036
    }
3037
    else
3038
        *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
    nPos = CSLFindName(papszParams, "password");
2✔
3050
    if (nPos != -1)
2✔
3051
    {
3052
        *ppszPassword =
×
3053
            CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
×
3054
    }
3055
    else if (CPLGetConfigOption("PGPASSWORD", nullptr) != nullptr)
2✔
3056
    {
3057
        *ppszPassword = CPLStrdup(CPLGetConfigOption("PGPASSWORD", nullptr));
2✔
3058
    }
3059
    else
3060
        *ppszPassword = nullptr;
×
3061

3062
    CSLDestroy(papszParams);
2✔
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
    return true;
2✔
3091
}
3092

3093
/***********************************************************************
3094
 * \brief Create a connection to a postgres database
3095
 **********************************************************************/
3096
static PGconn *GetConnection(const char *pszFilename,
2✔
3097
                             char **ppszConnectionString, char **ppszSchema,
3098
                             char **ppszTable, char **ppszColumn,
3099
                             char **ppszWhere, WorkingMode *nMode,
3100
                             GBool *bBrowseDatabase,
3101
                             OutDBResolution *peOutDBResolution)
3102
{
3103
    PGconn *poConn = nullptr;
2✔
3104
    char *pszService = nullptr;
2✔
3105
    char *pszDbname = nullptr;
2✔
3106
    char *pszHost = nullptr;
2✔
3107
    char *pszPort = nullptr;
2✔
3108
    char *pszUser = nullptr;
2✔
3109
    char *pszPassword = nullptr;
2✔
3110

3111
    if (GetConnectionInfo(pszFilename, ppszConnectionString, &pszService,
2✔
3112
                          &pszDbname, ppszSchema, ppszTable, ppszColumn,
3113
                          ppszWhere, &pszHost, &pszPort, &pszUser, &pszPassword,
3114
                          nMode, bBrowseDatabase, peOutDBResolution))
2✔
3115
    {
3116
        /**************************************************************
3117
         * Open a new database connection
3118
         **************************************************************/
3119
        poConn = PostGISRasterDriver::gpoPostGISRasterDriver->GetConnection(
2✔
3120
            *ppszConnectionString, pszService, pszDbname, pszHost, pszPort,
3121
            pszUser);
3122

3123
        if (poConn == nullptr)
2✔
3124
        {
3125
            CPLError(CE_Failure, CPLE_AppDefined,
×
3126
                     "Couldn't establish a database connection");
3127
        }
3128
    }
3129

3130
    CPLFree(pszService);
2✔
3131
    CPLFree(pszDbname);
2✔
3132
    CPLFree(pszHost);
2✔
3133
    CPLFree(pszPort);
2✔
3134
    CPLFree(pszUser);
2✔
3135
    CPLFree(pszPassword);
2✔
3136

3137
    return poConn;
2✔
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
GDALDataset *PostGISRasterDataset::Open(GDALOpenInfo *poOpenInfo)
2✔
3152
{
3153
    char *pszConnectionString = nullptr;
2✔
3154
    char *pszSchema = nullptr;
2✔
3155
    char *pszTable = nullptr;
2✔
3156
    char *pszColumn = nullptr;
2✔
3157
    char *pszWhere = nullptr;
2✔
3158
    WorkingMode nMode = NO_MODE;
2✔
3159
    PGconn *poConn = nullptr;
2✔
3160
    PostGISRasterDataset *poDS = nullptr;
2✔
3161
    GBool bBrowseDatabase = false;
2✔
3162
    OutDBResolution eOutDBResolution;
3163

3164
    /**************************
3165
     * Check input parameter
3166
     **************************/
3167
    if (!PostGISRasterDriverIdentify(poOpenInfo))
2✔
3168
        return nullptr;
×
3169

3170
    poConn = GetConnection(poOpenInfo->pszFilename, &pszConnectionString,
2✔
3171
                           &pszSchema, &pszTable, &pszColumn, &pszWhere, &nMode,
3172
                           &bBrowseDatabase, &eOutDBResolution);
3173
    if (poConn == nullptr)
2✔
3174
    {
3175
        CPLFree(pszConnectionString);
×
3176
        CPLFree(pszSchema);
×
3177
        CPLFree(pszTable);
×
3178
        CPLFree(pszColumn);
×
3179
        CPLFree(pszWhere);
×
3180
        return nullptr;
×
3181
    }
3182

3183
    /* For CLIENT_SIDE_IF_POSSIBLE mode, check if PostGIS 2.5 / ST_BandFileSize
3184
     */
3185
    /* is available */
3186
    bool bHasStBandFileSize = false;
2✔
3187
    if (eOutDBResolution == OutDBResolution::CLIENT_SIDE_IF_POSSIBLE)
2✔
3188
    {
3189
        const CPLString osCommand(
3190
            "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
        PGresult *poResult = PQexec(poConn, osCommand);
×
3197
        if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
×
3198
            PQntuples(poResult) == 1)
×
3199
        {
3200
#ifdef DEBUG_VERBOSE
3201
            CPLDebug("PostGIS_Raster", "ST_BandFileSize available");
3202
#endif
3203
            bHasStBandFileSize = true;
×
3204
        }
3205
        else if (poResult && PQresultStatus(poResult) != PGRES_TUPLES_OK)
×
3206
        {
3207
            CPLDebug("PostGIS_Raster", "PostGISRasterDataset::Open(): %s",
×
3208
                     PQerrorMessage(poConn));
3209
        }
3210

3211
        if (poResult)
×
3212
            PQclear(poResult);
×
3213
    }
3214

3215
    /*******************************************************************
3216
     * No table will be read. Only shows information about the existent
3217
     * raster tables
3218
     ******************************************************************/
3219
    if (bBrowseDatabase)
2✔
3220
    {
3221
        /**
3222
         * Creates empty dataset object, only for subdatasets
3223
         **/
3224
        poDS = new PostGISRasterDataset();
×
3225
        poDS->poConn = poConn;
×
3226
        poDS->eAccess = GA_ReadOnly;
×
3227
        // poDS->poDriver = poDriver;
3228
        poDS->nMode = (pszSchema) ? BROWSE_SCHEMA : BROWSE_DATABASE;
×
3229
        poDS->eOutDBResolution = eOutDBResolution;
×
3230
        poDS->bHasStBandFileSize = bHasStBandFileSize;
×
3231

3232
        /**
3233
         * Look for raster tables at database and
3234
         * store them as subdatasets
3235
         **/
3236
        if (!poDS->BrowseDatabase(pszSchema, pszConnectionString))
×
3237
        {
3238
            CPLFree(pszConnectionString);
×
3239
            delete poDS;
×
3240

3241
            if (pszSchema)
×
3242
                CPLFree(pszSchema);
×
3243
            if (pszTable)
×
3244
                CPLFree(pszTable);
×
3245
            if (pszColumn)
×
3246
                CPLFree(pszColumn);
×
3247
            if (pszWhere)
×
3248
                CPLFree(pszWhere);
×
3249

3250
            return nullptr;
×
3251
        }
3252

3253
        if (pszSchema)
×
3254
            CPLFree(pszSchema);
×
3255
        if (pszTable)
×
3256
            CPLFree(pszTable);
×
3257
        if (pszColumn)
×
3258
            CPLFree(pszColumn);
×
3259
        if (pszWhere)
×
3260
            CPLFree(pszWhere);
×
3261
    }
3262

3263
    /*******************************************************************
3264
     * A table will be read as dataset: Fetch raster properties from db.
3265
     ******************************************************************/
3266
    else
3267
    {
3268
        poDS = new PostGISRasterDataset();
2✔
3269
        poDS->poConn = poConn;
2✔
3270
        poDS->eAccess = poOpenInfo->eAccess;
2✔
3271
        poDS->nMode = nMode;
2✔
3272
        poDS->eOutDBResolution = eOutDBResolution;
2✔
3273
        poDS->bHasStBandFileSize = bHasStBandFileSize;
2✔
3274
        // poDS->poDriver = poDriver;
3275

3276
        poDS->pszSchema = pszSchema;
2✔
3277
        poDS->pszTable = pszTable;
2✔
3278
        poDS->pszColumn = pszColumn;
2✔
3279
        poDS->pszWhere = pszWhere;
2✔
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
        if (!poDS->SetRasterProperties(pszConnectionString))
2✔
3290
        {
3291
            CPLFree(pszConnectionString);
2✔
3292
            delete poDS;
2✔
3293
            return nullptr;
2✔
3294
        }
3295
    }
3296

3297
    CPLFree(pszConnectionString);
×
3298
    return poDS;
×
3299
}
3300

3301
/************************************************************************/
3302
/*                      GetMetadataDomainList()                         */
3303
/************************************************************************/
3304

3305
char **PostGISRasterDataset::GetMetadataDomainList()
×
3306
{
3307
    return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
×
3308
                                   "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
char **PostGISRasterDataset::GetMetadata(const char *pszDomain)
×
3317
{
3318
    if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
×
3319
        return papszSubdatasets;
×
3320
    else
3321
        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
const OGRSpatialReference *PostGISRasterDataset::GetSpatialRef() const
×
3331
{
3332

3333
    if (nSrid == -1)
×
3334
        return nullptr;
×
3335

3336
    if (!m_oSRS.IsEmpty())
×
3337
        return &m_oSRS;
×
3338

3339
    /********************************************************
3340
     *          Reading proj from database
3341
     ********************************************************/
3342
    CPLString osCommand;
×
3343
    osCommand.Printf("SELECT srtext FROM spatial_ref_sys where SRID=%d", nSrid);
×
3344
    PGresult *poResult = PQexec(this->poConn, osCommand.c_str());
×
3345
    if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
×
3346
        PQntuples(poResult) > 0)
×
3347
    {
3348
        const char *pszProjection = PQgetvalue(poResult, 0, 0);
×
3349
        if (pszProjection && pszProjection[0])
×
3350
            m_oSRS.importFromWkt(pszProjection);
×
3351
    }
3352

3353
    if (poResult)
×
3354
        PQclear(poResult);
×
3355

3356
    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
CPLErr PostGISRasterDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
×
3364
{
3365

3366
    if (poSRS == nullptr)
×
3367
        return CE_None;
×
3368

3369
    CPLString osCommand;
×
3370

3371
    /*****************************************************************
3372
     * Check if the dataset allows updating
3373
     *****************************************************************/
3374
    if (GetAccess() != GA_Update)
×
3375
    {
3376
        ReportError(CE_Failure, CPLE_NoWriteAccess,
×
3377
                    "This driver doesn't allow write access");
3378
        return CE_Failure;
×
3379
    }
3380

3381
    /*****************************************************************
3382
     * Look for projection with this text
3383
     *****************************************************************/
3384

3385
    char *pszWKT = nullptr;
×
3386
    poSRS->exportToWkt(&pszWKT);
×
3387
    if (pszWKT == nullptr)
×
3388
        return CE_Failure;
×
3389

3390
    osCommand.Printf("SELECT srid FROM spatial_ref_sys where srtext='%s'",
×
3391
                     pszWKT);
×
3392
    CPLFree(pszWKT);
×
3393
    PGresult *poResult = PQexec(poConn, osCommand.c_str());
×
3394

3395
    if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
×
3396
        PQntuples(poResult) > 0)
×
3397
    {
3398

3399
        const int nFetchedSrid = atoi(PQgetvalue(poResult, 0, 0));
×
3400

3401
        // update class attribute
3402
        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
                         nSrid, pszTable, pszColumn);
×
3408
        poResult = PQexec(poConn, osCommand.c_str());
×
3409
        if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3410
        {
3411
            ReportError(CE_Failure, CPLE_AppDefined,
×
3412
                        "Couldn't update raster_columns table: %s",
3413
                        PQerrorMessage(poConn));
×
3414
            return CE_Failure;
×
3415
        }
3416

3417
        // TODO: Update ALL blocks with the new srid...
3418

3419
        return CE_None;
×
3420
    }
3421
    else
3422
    {
3423
        ReportError(CE_Failure, CPLE_WrongFormat,
×
3424
                    "Couldn't find WKT definition");
3425
        return CE_Failure;
×
3426
    }
3427
}
3428

3429
/********************************************************
3430
 * \brief Set the affine transformation coefficients
3431
 ********************************************************/
3432
CPLErr PostGISRasterDataset::SetGeoTransform(const GDALGeoTransform &gt)
×
3433
{
3434
    m_gt = gt;
×
3435
    return CE_None;
×
3436
}
3437

3438
/********************************************************
3439
 * \brief Get the affine transformation coefficients
3440
 ********************************************************/
3441
CPLErr PostGISRasterDataset::GetGeoTransform(GDALGeoTransform &gt) const
×
3442
{
3443

3444
    // copy necessary values in supplied buffer
3445
    gt = m_gt;
×
3446

3447
    if (nRasterXSize == 0 && nRasterYSize == 0)
×
3448
        return CE_Failure;
×
3449

3450
    /* To avoid QGIS trying to create a warped VRT for what is really */
3451
    /* an ungeoreferenced dataset */
3452
    if (m_gt == GDALGeoTransform())
×
3453
    {
3454
        return CE_Failure;
×
3455
    }
3456

3457
    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
char **PostGISRasterDataset::GetFileList()
×
3468
{
3469
    return nullptr;
×
3470
}
3471

3472
/********************************************************
3473
 * \brief Create a copy of a PostGIS Raster dataset.
3474
 ********************************************************/
3475
GDALDataset *PostGISRasterDataset::CreateCopy(
18✔
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
    char *pszSchema = nullptr;
18✔
3481
    char *pszTable = nullptr;
18✔
3482
    char *pszColumn = nullptr;
18✔
3483
    char *pszWhere = nullptr;
18✔
3484
    GBool bBrowseDatabase = false;
18✔
3485
    WorkingMode nMode;
3486
    OutDBResolution eOutDBResolution;
3487
    char *pszConnectionString = nullptr;
18✔
3488
    PGconn *poConn = nullptr;
18✔
3489
    PGresult *poResult = nullptr;
18✔
3490
    GBool bInsertSuccess;
3491

3492
    CPLString osCommand;
36✔
3493

3494
    if (poGSrcDS->GetDriver() != GDALGetDriverByName("PostGISRaster"))
18✔
3495
    {
3496
        CPLError(CE_Failure, CPLE_NotSupported,
18✔
3497
                 "PostGISRasterDataset::CreateCopy() only works on source "
3498
                 "datasets that are PostGISRaster");
3499
        return nullptr;
18✔
3500
    }
3501

3502
    // Now we can do the cast
3503
    PostGISRasterDataset *poSrcDS =
3504
        cpl::down_cast<PostGISRasterDataset *>(poGSrcDS);
×
3505

3506
    // Check connection string
3507
    if (pszFilename == nullptr || !STARTS_WITH_CI(pszFilename, "PG:"))
×
3508
    {
3509
        /**
3510
         * The connection string provided is not a valid connection
3511
         * string.
3512
         */
3513
        CPLError(CE_Failure, CPLE_NotSupported,
×
3514
                 "PostGIS Raster driver was unable to parse the provided "
3515
                 "connection string.");
3516
        return nullptr;
×
3517
    }
3518

3519
    poConn = GetConnection(pszFilename, &pszConnectionString, &pszSchema,
×
3520
                           &pszTable, &pszColumn, &pszWhere, &nMode,
3521
                           &bBrowseDatabase, &eOutDBResolution);
3522
    if (poConn == nullptr || bBrowseDatabase || pszTable == nullptr)
×
3523
    {
3524
        CPLFree(pszConnectionString);
×
3525
        CPLFree(pszSchema);
×
3526
        CPLFree(pszTable);
×
3527
        CPLFree(pszColumn);
×
3528
        CPLFree(pszWhere);
×
3529

3530
        // if connection info fails, browsing mode, or no table set
3531
        return nullptr;
×
3532
    }
3533

3534
    CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
×
3535
    CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
×
3536
    CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
×
3537

3538
    // begin transaction
3539
    poResult = PQexec(poConn, "begin");
×
3540
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3541
    {
3542
        CPLError(CE_Failure, CPLE_AppDefined,
×
3543
                 "Error beginning database transaction: %s",
3544
                 PQerrorMessage(poConn));
3545
        if (poResult != nullptr)
×
3546
            PQclear(poResult);
×
3547
        CPLFree(pszSchema);
×
3548
        CPLFree(pszTable);
×
3549
        CPLFree(pszColumn);
×
3550
        CPLFree(pszWhere);
×
3551

3552
        CPLFree(pszConnectionString);
×
3553

3554
        return nullptr;
×
3555
    }
3556

3557
    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
    osCommand.Printf("create table if not exists %s.%s (rid serial, %s "
×
3564
                     "raster, constraint %s_pkey primary key (rid));",
3565
                     pszSchema, pszTable, pszColumn, pszTable);
×
3566
    poResult = PQexec(poConn, osCommand.c_str());
×
3567
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3568
    {
3569

3570
        CPLError(CE_Failure, CPLE_AppDefined,
×
3571
                 "Error creating needed tables: %s", PQerrorMessage(poConn));
3572
        if (poResult != nullptr)
×
3573
            PQclear(poResult);
×
3574

3575
        // rollback
3576
        poResult = PQexec(poConn, "rollback");
×
3577
        if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3578
        {
3579

3580
            CPLError(CE_Failure, CPLE_AppDefined,
×
3581
                     "Error rolling back transaction: %s",
3582
                     PQerrorMessage(poConn));
3583
        }
3584
        if (poResult != nullptr)
×
3585
            PQclear(poResult);
×
3586
        if (pszSchema)
×
3587
            CPLFree(pszSchema);
×
3588
        if (pszTable)
×
3589
            CPLFree(pszTable);
×
3590
        if (pszColumn)
×
3591
            CPLFree(pszColumn);
×
3592
        if (pszWhere)
×
3593
            CPLFree(pszWhere);
×
3594

3595
        CPLFree(pszConnectionString);
×
3596

3597
        return nullptr;
×
3598
    }
3599

3600
    PQclear(poResult);
×
3601

3602
    CPLString osIdxNameI;
×
3603
    osIdxNameI.Printf("%s_%s_gist", pszTable, pszColumn);
×
3604
    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
                     osColumnI.c_str());
×
3610
    poResult = PQexec(poConn, osCommand.c_str());
×
3611
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3612
    {
3613

3614
        CPLError(CE_Failure, CPLE_AppDefined, "Error creating needed index: %s",
×
3615
                 PQerrorMessage(poConn));
3616
        if (poResult != nullptr)
×
3617
            PQclear(poResult);
×
3618

3619
        // rollback
3620
        poResult = PQexec(poConn, "rollback");
×
3621
        if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3622
        {
3623

3624
            CPLError(CE_Failure, CPLE_AppDefined,
×
3625
                     "Error rolling back transaction: %s",
3626
                     PQerrorMessage(poConn));
3627
        }
3628
        if (poResult != nullptr)
×
3629
            PQclear(poResult);
×
3630
        if (pszSchema)
×
3631
            CPLFree(pszSchema);
×
3632
        if (pszTable)
×
3633
            CPLFree(pszTable);
×
3634
        if (pszColumn)
×
3635
            CPLFree(pszColumn);
×
3636
        if (pszWhere)
×
3637
            CPLFree(pszWhere);
×
3638

3639
        CPLFree(pszConnectionString);
×
3640

3641
        return nullptr;
×
3642
    }
3643

3644
    PQclear(poResult);
×
3645

3646
    const char *pszSubdatasetName = nullptr;
×
3647
    PostGISRasterDataset *poSubDS = nullptr;
×
3648
    if (poSrcDS->nMode == ONE_RASTER_PER_TABLE)
×
3649
    {
3650
        // one raster per table
3651

3652
        // insert one raster
3653
        bInsertSuccess =
3654
            InsertRaster(poConn, poSrcDS, pszSchema, pszTable, pszColumn);
×
3655
        if (!bInsertSuccess)
×
3656
        {
3657
            // rollback
3658
            poResult = PQexec(poConn, "rollback");
×
3659
            if (poResult == nullptr ||
×
3660
                PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3661
            {
3662

3663
                CPLError(CE_Failure, CPLE_AppDefined,
×
3664
                         "Error rolling back transaction: %s",
3665
                         PQerrorMessage(poConn));
3666
            }
3667
            if (poResult != nullptr)
×
3668
                PQclear(poResult);
×
3669
            if (pszSchema)
×
3670
                CPLFree(pszSchema);
×
3671
            if (pszTable)
×
3672
                CPLFree(pszTable);
×
3673
            if (pszColumn)
×
3674
                CPLFree(pszColumn);
×
3675
            if (pszWhere)
×
3676
                CPLFree(pszWhere);
×
3677

3678
            CPLFree(pszConnectionString);
×
3679

3680
            return nullptr;
×
3681
        }
3682
    }
3683
    else if (poSrcDS->nMode == ONE_RASTER_PER_ROW)
×
3684
    {
3685
        // one raster per row
3686

3687
        // papszSubdatasets contains name/desc for each subdataset
3688
        for (int i = 0; i < CSLCount(poSrcDS->papszSubdatasets); i += 2)
×
3689
        {
3690
            pszSubdatasetName =
3691
                CPLParseNameValue(poSrcDS->papszSubdatasets[i], nullptr);
×
3692
            if (pszSubdatasetName == nullptr)
×
3693
            {
3694
                CPLDebug("PostGIS_Raster",
×
3695
                         "PostGISRasterDataset::CreateCopy(): "
3696
                         "Could not parse name/value out of subdataset list: "
3697
                         "%s",
3698
                         poSrcDS->papszSubdatasets[i]);
×
3699
                continue;
×
3700
            }
3701

3702
            // for each subdataset
3703
            GDALOpenInfo poOpenInfo(pszSubdatasetName, GA_ReadOnly);
×
3704
            // open the subdataset
3705
            poSubDS = cpl::down_cast<PostGISRasterDataset *>(Open(&poOpenInfo));
×
3706

3707
            if (poSubDS == nullptr)
×
3708
            {
3709
                // notify!
3710
                CPLDebug("PostGIS_Raster",
×
3711
                         "PostGISRasterDataset::CreateCopy(): "
3712
                         "Could not open a subdataset: %s",
3713
                         pszSubdatasetName);
3714
                continue;
×
3715
            }
3716

3717
            // insert one raster
3718
            bInsertSuccess =
3719
                InsertRaster(poConn, poSubDS, pszSchema, pszTable, pszColumn);
×
3720

3721
            if (!bInsertSuccess)
×
3722
            {
3723
                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
            GDALClose(GDALDataset::ToHandle(poSubDS));
×
3732
        }
3733
    }
3734

3735
    // commit transaction
3736
    poResult = PQexec(poConn, "commit");
×
3737
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3738
    {
3739
        CPLError(CE_Failure, CPLE_AppDefined,
×
3740
                 "Error committing database transaction: %s",
3741
                 PQerrorMessage(poConn));
3742
        if (poResult != nullptr)
×
3743
            PQclear(poResult);
×
3744
        if (pszSchema)
×
3745
            CPLFree(pszSchema);
×
3746
        if (pszTable)
×
3747
            CPLFree(pszTable);
×
3748
        if (pszColumn)
×
3749
            CPLFree(pszColumn);
×
3750
        if (pszWhere)
×
3751
            CPLFree(pszWhere);
×
3752

3753
        CPLFree(pszConnectionString);
×
3754

3755
        return nullptr;
×
3756
    }
3757

3758
    PQclear(poResult);
×
3759

3760
    if (pszSchema)
×
3761
        CPLFree(pszSchema);
×
3762
    if (pszTable)
×
3763
        CPLFree(pszTable);
×
3764
    if (pszColumn)
×
3765
        CPLFree(pszColumn);
×
3766
    if (pszWhere)
×
3767
        CPLFree(pszWhere);
×
3768

3769
    CPLFree(pszConnectionString);
×
3770

3771
    CPLDebug("PostGIS_Raster",
×
3772
             "PostGISRasterDataset::CreateCopy(): "
3773
             "Opening new dataset: %s",
3774
             pszFilename);
3775

3776
    // connect to the new dataset
3777
    GDALOpenInfo poOpenInfo(pszFilename, GA_Update);
×
3778
    // open the newdataset
3779
    poSubDS = cpl::down_cast<PostGISRasterDataset *>(Open(&poOpenInfo));
×
3780

3781
    if (poSubDS == nullptr)
×
3782
    {
3783
        CPLDebug("PostGIS_Raster", "PostGISRasterDataset::CreateCopy(): "
×
3784
                                   "New dataset could not be opened.");
3785
    }
3786

3787
    return poSubDS;
×
3788
}
3789

3790
/********************************************************
3791
 * \brief Helper method to insert a new raster.
3792
 ********************************************************/
3793
GBool PostGISRasterDataset::InsertRaster(PGconn *poConn,
×
3794
                                         PostGISRasterDataset *poSrcDS,
3795
                                         const char *pszSchema,
3796
                                         const char *pszTable,
3797
                                         const char *pszColumn)
3798
{
3799
    CPLString osCommand;
×
3800
    PGresult *poResult = nullptr;
×
3801

3802
    CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
×
3803
    CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
×
3804
    CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
×
3805
    CPLString osSrcSchemaI(CPLQuotedSQLIdentifier(poSrcDS->pszSchema));
×
3806
    CPLString osSrcTableI(CPLQuotedSQLIdentifier(poSrcDS->pszTable));
×
3807
    CPLString osSrcColumnI(CPLQuotedSQLIdentifier(poSrcDS->pszColumn));
×
3808

3809
    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
                         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
            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
    poResult = PQexec(poConn, osCommand.c_str());
×
3832
    if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3833
    {
3834

3835
        CPLError(CE_Failure, CPLE_AppDefined, "Error inserting raster: %s",
×
3836
                 PQerrorMessage(poConn));
3837
        if (poResult != nullptr)
×
3838
            PQclear(poResult);
×
3839

3840
        return false;
×
3841
    }
3842

3843
    PQclear(poResult);
×
3844

3845
    return true;
×
3846
}
3847

3848
/*********************************************************
3849
 * \brief Delete a PostGIS Raster dataset.
3850
 *********************************************************/
3851
CPLErr PostGISRasterDataset::Delete(const char *pszFilename)
×
3852
{
3853
    char *pszSchema = nullptr;
×
3854
    char *pszTable = nullptr;
×
3855
    char *pszColumn = nullptr;
×
3856
    char *pszWhere = nullptr;
×
3857
    GBool bBrowseDatabase;
3858
    char *pszConnectionString = nullptr;
×
3859
    WorkingMode nMode;
3860
    OutDBResolution eOutDBResolution;
3861
    PGconn *poConn = nullptr;
×
3862
    CPLString osCommand;
×
3863
    CPLErr nError = CE_Failure;
×
3864

3865
    // Check connection string
3866
    if (pszFilename == nullptr || !STARTS_WITH_CI(pszFilename, "PG:"))
×
3867
    {
3868
        /**
3869
         * The connection string provided is not a valid connection
3870
         * string.
3871
         */
3872
        CPLError(CE_Failure, CPLE_NotSupported,
×
3873
                 "PostGIS Raster driver was unable to parse the provided "
3874
                 "connection string. Nothing was deleted.");
3875
        return CE_Failure;
×
3876
    }
3877

3878
    poConn = GetConnection(pszFilename, &pszConnectionString, &pszSchema,
×
3879
                           &pszTable, &pszColumn, &pszWhere, &nMode,
3880
                           &bBrowseDatabase, &eOutDBResolution);
3881
    if (poConn == nullptr || pszSchema == nullptr || pszTable == nullptr)
×
3882
    {
3883
        CPLFree(pszConnectionString);
×
3884
        CPLFree(pszSchema);
×
3885
        CPLFree(pszTable);
×
3886
        CPLFree(pszColumn);
×
3887
        CPLFree(pszWhere);
×
3888

3889
        return CE_Failure;
×
3890
    }
3891

3892
    // begin transaction
3893
    {
3894
        PGresult *poResult = PQexec(poConn, "begin");
×
3895
        if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3896
        {
3897
            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
            nMode = NO_MODE;
×
3903
        }
3904

3905
        PQclear(poResult);
×
3906
    }
3907

3908
    if (nMode == ONE_RASTER_PER_TABLE ||
×
3909
        (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
        osCommand.Printf("drop table %s.%s", pszSchema, pszTable);
×
3916
        PGresult *poResult = PQexec(poConn, osCommand.c_str());
×
3917
        if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3918
        {
3919
            CPLError(CE_Failure, CPLE_AppDefined,
×
3920
                     "Couldn't drop the table %s.%s: %s", pszSchema, pszTable,
3921
                     PQerrorMessage(poConn));
3922
        }
3923
        else
3924
        {
3925
            nError = CE_None;
×
3926
        }
3927
        if (poResult)
×
3928
            PQclear(poResult);
×
3929
    }
3930
    else if (nMode == ONE_RASTER_PER_ROW)
×
3931
    {
3932

3933
        // delete from <schema>.<table> where <where>
3934
        osCommand.Printf("delete from %s.%s where %s", pszSchema, pszTable,
×
3935
                         pszWhere);
×
3936
        PGresult *poResult = PQexec(poConn, osCommand.c_str());
×
3937
        if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3938
        {
3939
            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
            nError = CE_None;
×
3946
        }
3947
        if (poResult)
×
3948
            PQclear(poResult);
×
3949
    }
3950

3951
    // if mode == NO_MODE, the begin transaction above did not complete,
3952
    // so no commit is necessary
3953
    if (nMode != NO_MODE)
×
3954
    {
3955
        PGresult *poResult = PQexec(poConn, "commit");
×
3956
        if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
×
3957
        {
3958

3959
            CPLError(CE_Failure, CPLE_AppDefined,
×
3960
                     "Error committing database transaction: %s",
3961
                     PQerrorMessage(poConn));
3962

3963
            nError = CE_Failure;
×
3964
        }
3965
        if (poResult)
×
3966
            PQclear(poResult);
×
3967
    }
3968

3969
    CPLFree(pszSchema);
×
3970
    CPLFree(pszTable);
×
3971
    CPLFree(pszColumn);
×
3972
    CPLFree(pszWhere);
×
3973

3974
    // clean up connection string
3975
    CPLFree(pszConnectionString);
×
3976

3977
    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
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
    int ulx = nXOff;
×
3990
    int uly = nYOff;
×
3991
    int lrx = nXEndOff;
×
3992
    int lry = nYEndOff;
×
3993

3994
    double xRes = m_gt[GEOTRSFRM_WE_RES];
×
3995
    double yRes = m_gt[GEOTRSFRM_NS_RES];
×
3996

3997
    adfProjWin[0] = m_gt[GEOTRSFRM_TOPLEFT_X] + ulx * xRes +
×
3998
                    uly * m_gt[GEOTRSFRM_ROTATION_PARAM1];
×
3999
    adfProjWin[1] = m_gt[GEOTRSFRM_TOPLEFT_Y] +
×
4000
                    ulx * m_gt[GEOTRSFRM_ROTATION_PARAM2] + uly * yRes;
×
4001
    adfProjWin[2] = m_gt[GEOTRSFRM_TOPLEFT_X] + lrx * xRes +
×
4002
                    uly * m_gt[GEOTRSFRM_ROTATION_PARAM1];
×
4003
    adfProjWin[3] = m_gt[GEOTRSFRM_TOPLEFT_Y] +
×
4004
                    lrx * m_gt[GEOTRSFRM_ROTATION_PARAM2] + uly * yRes;
×
4005
    adfProjWin[4] = m_gt[GEOTRSFRM_TOPLEFT_X] + lrx * xRes +
×
4006
                    lry * m_gt[GEOTRSFRM_ROTATION_PARAM1];
×
4007
    adfProjWin[5] = m_gt[GEOTRSFRM_TOPLEFT_Y] +
×
4008
                    lrx * m_gt[GEOTRSFRM_ROTATION_PARAM2] + lry * yRes;
×
4009
    adfProjWin[6] = m_gt[GEOTRSFRM_TOPLEFT_X] + ulx * xRes +
×
4010
                    lry * m_gt[GEOTRSFRM_ROTATION_PARAM1];
×
4011
    adfProjWin[7] = m_gt[GEOTRSFRM_TOPLEFT_Y] +
×
4012
                    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
    return true;
×
4025
}
4026

4027
/***********************************************************************
4028
 * GDALRegister_PostGISRaster()
4029
 **********************************************************************/
4030
void GDALRegister_PostGISRaster()
10✔
4031

4032
{
4033
    if (!GDAL_CHECK_VERSION("PostGISRaster driver"))
10✔
4034
        return;
×
4035

4036
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
10✔
4037
        return;
×
4038

4039
    GDALDriver *poDriver = new PostGISRasterDriver();
10✔
4040
    PostGISRasterDriverSetCommonMetadata(poDriver);
10✔
4041

4042
    poDriver->pfnOpen = PostGISRasterDataset::Open;
10✔
4043
    poDriver->pfnCreateCopy = PostGISRasterDataset::CreateCopy;
10✔
4044
    poDriver->pfnDelete = PostGISRasterDataset::Delete;
10✔
4045

4046
    GetGDALDriverManager()->RegisterDriver(poDriver);
10✔
4047
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc