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