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