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