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