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