Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: High Performance Image Reprojector
4 : * Purpose: Test program for high performance warper API.
5 : * Author: Frank Warmerdam <warmerdam@pobox.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2002, i3 - information integration and imaging
9 : * Fort Collins, CO
10 : * Copyright (c) 2007-2015, Even Rouault <even dot rouault at spatialys.com>
11 : * Copyright (c) 2015, Faza Mahamood
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : #include "cpl_port.h"
17 : #include "gdal_utils.h"
18 : #include "gdal_utils_priv.h"
19 : #include "gdalargumentparser.h"
20 :
21 : #include <cctype>
22 : #include <cmath>
23 : #include <cstdio>
24 : #include <cstdlib>
25 : #include <cstring>
26 :
27 : #include <algorithm>
28 : #include <array>
29 : #include <limits>
30 : #include <set>
31 : #include <utility>
32 : #include <vector>
33 :
34 : // Suppress deprecation warning for GDALOpenVerticalShiftGrid and
35 : // GDALApplyVerticalShiftGrid
36 : #ifndef CPL_WARN_DEPRECATED_GDALOpenVerticalShiftGrid
37 : #define CPL_WARN_DEPRECATED_GDALOpenVerticalShiftGrid(x)
38 : #define CPL_WARN_DEPRECATED_GDALApplyVerticalShiftGrid(x)
39 : #endif
40 :
41 : #include "commonutils.h"
42 : #include "cpl_conv.h"
43 : #include "cpl_error.h"
44 : #include "cpl_progress.h"
45 : #include "cpl_string.h"
46 : #include "gdal.h"
47 : #include "gdal_alg.h"
48 : #include "gdal_alg_priv.h"
49 : #include "gdal_priv.h"
50 : #include "gdalwarper.h"
51 : #include "ogr_api.h"
52 : #include "ogr_core.h"
53 : #include "ogr_geometry.h"
54 : #include "ogr_spatialref.h"
55 : #include "ogr_srs_api.h"
56 : #include "ogr_proj_p.h"
57 : #include "ogrct_priv.h"
58 : #include "ogrsf_frmts.h"
59 : #include "vrtdataset.h"
60 : #include "../frmts/gtiff/cogdriver.h"
61 :
62 : /************************************************************************/
63 : /* GDALWarpAppOptions */
64 : /************************************************************************/
65 :
66 : /** Options for use with GDALWarp(). GDALWarpAppOptions* must be allocated and
67 : * freed with GDALWarpAppOptionsNew() and GDALWarpAppOptionsFree() respectively.
68 : */
69 : struct GDALWarpAppOptions
70 : {
71 : /*! Raw program arguments */
72 : CPLStringList aosArgs{};
73 :
74 : /*! set georeferenced extents of output file to be created (in target SRS by
75 : default, or in the SRS specified with pszTE_SRS) */
76 : double dfMinX = 0;
77 : double dfMinY = 0;
78 : double dfMaxX = 0;
79 : double dfMaxY = 0;
80 :
81 : /*! the SRS in which to interpret the coordinates given in
82 : GDALWarpAppOptions::dfMinX, GDALWarpAppOptions::dfMinY,
83 : GDALWarpAppOptions::dfMaxX and GDALWarpAppOptions::dfMaxY. The SRS may be
84 : any of the usual GDAL/OGR forms, complete WKT, PROJ.4, EPSG:n or a file
85 : containing the WKT. It is a convenience e.g. when knowing the output
86 : coordinates in a geodetic long/lat SRS, but still wanting a result in a
87 : projected coordinate system. */
88 : std::string osTE_SRS{};
89 :
90 : /*! set output file resolution (in target georeferenced units) */
91 : double dfXRes = 0;
92 : double dfYRes = 0;
93 :
94 : /*! whether target pixels should have dfXRes == dfYRes */
95 : bool bSquarePixels = false;
96 :
97 : /*! align the coordinates of the extent of the output file to the values of
98 : the GDALWarpAppOptions::dfXRes and GDALWarpAppOptions::dfYRes, such that
99 : the aligned extent includes the minimum extent. */
100 : bool bTargetAlignedPixels = false;
101 :
102 : /*! set output file size in pixels and lines. If
103 : GDALWarpAppOptions::nForcePixels or GDALWarpAppOptions::nForceLines is
104 : set to 0, the other dimension will be guessed from the computed
105 : resolution. Note that GDALWarpAppOptions::nForcePixels and
106 : GDALWarpAppOptions::nForceLines cannot be used with
107 : GDALWarpAppOptions::dfXRes and GDALWarpAppOptions::dfYRes. */
108 : int nForcePixels = 0;
109 : int nForceLines = 0;
110 :
111 : /*! allow or suppress progress monitor and other non-error output */
112 : bool bQuiet = true;
113 :
114 : /*! the progress function to use */
115 : GDALProgressFunc pfnProgress = GDALDummyProgress;
116 :
117 : /*! pointer to the progress data variable */
118 : void *pProgressData = nullptr;
119 :
120 : /*! creates an output alpha band to identify nodata (unset/transparent)
121 : pixels when set to true */
122 : bool bEnableDstAlpha = false;
123 :
124 : /*! forces the last band of an input file to be considered as alpha band. */
125 : bool bEnableSrcAlpha = false;
126 :
127 : /*! Prevent a source alpha band from being considered as such */
128 : bool bDisableSrcAlpha = false;
129 :
130 : /*! output format. Use the short format name. */
131 : std::string osFormat{};
132 :
133 : bool bCreateOutput = false;
134 :
135 : /*! list of warp options. ("NAME1=VALUE1","NAME2=VALUE2",...). The
136 : GDALWarpOptions::aosWarpOptions docs show all options. */
137 : CPLStringList aosWarpOptions{};
138 :
139 : double dfErrorThreshold = -1;
140 :
141 : /*! the amount of memory (in megabytes) that the warp API is allowed
142 : to use for caching. */
143 : double dfWarpMemoryLimit = 0;
144 :
145 : /*! list of create options for the output format driver. See format
146 : specific documentation for legal creation options for each format. */
147 : CPLStringList aosCreateOptions{};
148 :
149 : /*! the data type of the output bands */
150 : GDALDataType eOutputType = GDT_Unknown;
151 :
152 : /*! working pixel data type. The data type of pixels in the source
153 : image and destination image buffers. */
154 : GDALDataType eWorkingType = GDT_Unknown;
155 :
156 : /*! the resampling method. Available methods are: near, bilinear,
157 : cubic, cubicspline, lanczos, average, mode, max, min, med,
158 : q1, q3, sum */
159 : GDALResampleAlg eResampleAlg = GRA_NearestNeighbour;
160 :
161 : /*! whether -r was specified */
162 : bool bResampleAlgSpecifiedByUser = false;
163 :
164 : /*! nodata masking values for input bands (different values can be supplied
165 : for each band). ("value1 value2 ..."). Masked values will not be used
166 : in interpolation. Use a value of "None" to ignore intrinsic nodata
167 : settings on the source dataset. */
168 : std::string osSrcNodata{};
169 :
170 : /*! nodata values for output bands (different values can be supplied for
171 : each band). ("value1 value2 ..."). New files will be initialized to
172 : this value and if possible the nodata value will be recorded in the
173 : output file. Use a value of "None" to ensure that nodata is not defined.
174 : If this argument is not used then nodata values will be copied from
175 : the source dataset. */
176 : std::string osDstNodata{};
177 :
178 : /*! use multithreaded warping implementation. Multiple threads will be used
179 : to process chunks of image and perform input/output operation
180 : simultaneously. */
181 : bool bMulti = false;
182 :
183 : /*! list of transformer options suitable to pass to
184 : GDALCreateGenImgProjTransformer2().
185 : ("NAME1=VALUE1","NAME2=VALUE2",...) */
186 : CPLStringList aosTransformerOptions{};
187 :
188 : /*! enable use of a blend cutline from a vector dataset name or a WKT
189 : * geometry
190 : */
191 : std::string osCutlineDSNameOrWKT{};
192 :
193 : /*! cutline SRS */
194 : std::string osCutlineSRS{};
195 :
196 : /*! the named layer to be selected from the cutline datasource */
197 : std::string osCLayer{};
198 :
199 : /*! restrict desired cutline features based on attribute query */
200 : std::string osCWHERE{};
201 :
202 : /*! SQL query to select the cutline features instead of from a layer
203 : with osCLayer */
204 : std::string osCSQL{};
205 :
206 : /*! crop the extent of the target dataset to the extent of the cutline */
207 : bool bCropToCutline = false;
208 :
209 : /*! copy dataset and band metadata will be copied from the first source
210 : dataset. Items that differ between source datasets will be set "*" (see
211 : GDALWarpAppOptions::pszMDConflictValue) */
212 : bool bCopyMetadata = true;
213 :
214 : /*! copy band information from the first source dataset */
215 : bool bCopyBandInfo = true;
216 :
217 : /*! value to set metadata items that conflict between source datasets
218 : (default is "*"). Use "" to remove conflicting items. */
219 : std::string osMDConflictValue = "*";
220 :
221 : /*! set the color interpretation of the bands of the target dataset from the
222 : * source dataset */
223 : bool bSetColorInterpretation = false;
224 :
225 : /*! overview level of source files to be used */
226 : int nOvLevel = OVR_LEVEL_AUTO;
227 :
228 : /*! Whether to enable vertical shift adjustment */
229 : bool bVShift = false;
230 :
231 : /*! Whether to disable vertical shift adjustment */
232 : bool bNoVShift = false;
233 :
234 : /*! Source bands */
235 : std::vector<int> anSrcBands{};
236 :
237 : /*! Destination bands */
238 : std::vector<int> anDstBands{};
239 :
240 : /*! Used when using a temporary TIFF file while warping */
241 : bool bDeleteOutputFileOnceCreated = false;
242 :
243 : /*! set to true to customize error messages when called from "new" (GDAL 3.11) CLI or Algorithm API */
244 : bool bInvokedFromGdalAlgorithm = false;
245 : };
246 :
247 : static CPLErr
248 : LoadCutline(const std::string &osCutlineDSNameOrWKT, const std::string &osSRS,
249 : const std::string &oszCLayer, const std::string &osCWHERE,
250 : const std::string &osCSQL, OGRGeometryH *phCutlineRet);
251 : static CPLErr TransformCutlineToSource(GDALDataset *poSrcDS,
252 : OGRGeometry *poCutline,
253 : char ***ppapszWarpOptions,
254 : CSLConstList papszTO);
255 :
256 : static GDALDatasetH GDALWarpCreateOutput(
257 : int nSrcCount, GDALDatasetH *pahSrcDS, const char *pszFilename,
258 : const char *pszFormat, char **papszTO, CSLConstList papszCreateOptions,
259 : GDALDataType eDT, GDALTransformerArgUniquePtr &hTransformArg,
260 : bool bSetColorInterpretation, GDALWarpAppOptions *psOptions,
261 : bool bUpdateTransformerWithDestGT);
262 :
263 : static void RemoveConflictingMetadata(GDALMajorObjectH hObj,
264 : CSLConstList papszMetadata,
265 : const char *pszValueConflict);
266 :
267 3 : static double GetAverageSegmentLength(const OGRGeometry *poGeom)
268 : {
269 3 : if (!poGeom)
270 0 : return 0;
271 3 : switch (wkbFlatten(poGeom->getGeometryType()))
272 : {
273 1 : case wkbLineString:
274 : {
275 1 : const auto *poLS = poGeom->toLineString();
276 1 : double dfSum = 0;
277 1 : const int nPoints = poLS->getNumPoints();
278 1 : if (nPoints == 0)
279 0 : return 0;
280 5 : for (int i = 0; i < nPoints - 1; i++)
281 : {
282 4 : double dfX1 = poLS->getX(i);
283 4 : double dfY1 = poLS->getY(i);
284 4 : double dfX2 = poLS->getX(i + 1);
285 4 : double dfY2 = poLS->getY(i + 1);
286 4 : double dfDX = dfX2 - dfX1;
287 4 : double dfDY = dfY2 - dfY1;
288 4 : dfSum += sqrt(dfDX * dfDX + dfDY * dfDY);
289 : }
290 1 : return dfSum / nPoints;
291 : }
292 :
293 1 : case wkbPolygon:
294 : {
295 1 : if (poGeom->IsEmpty())
296 0 : return 0;
297 1 : double dfSum = 0;
298 2 : for (const auto *poLS : poGeom->toPolygon())
299 : {
300 1 : dfSum += GetAverageSegmentLength(poLS);
301 : }
302 1 : return dfSum / (1 + poGeom->toPolygon()->getNumInteriorRings());
303 : }
304 :
305 1 : case wkbMultiPolygon:
306 : case wkbMultiLineString:
307 : case wkbGeometryCollection:
308 : {
309 1 : if (poGeom->IsEmpty())
310 0 : return 0;
311 1 : double dfSum = 0;
312 2 : for (const auto *poSubGeom : poGeom->toGeometryCollection())
313 : {
314 1 : dfSum += GetAverageSegmentLength(poSubGeom);
315 : }
316 1 : return dfSum / poGeom->toGeometryCollection()->getNumGeometries();
317 : }
318 :
319 0 : default:
320 0 : return 0;
321 : }
322 : }
323 :
324 : /************************************************************************/
325 : /* FetchSrcMethod() */
326 : /************************************************************************/
327 :
328 3150 : static const char *FetchSrcMethod(CSLConstList papszTO,
329 : const char *pszDefault = nullptr)
330 : {
331 3150 : const char *pszMethod = CSLFetchNameValue(papszTO, "SRC_METHOD");
332 3150 : if (!pszMethod)
333 3074 : pszMethod = CSLFetchNameValueDef(papszTO, "METHOD", pszDefault);
334 3150 : return pszMethod;
335 : }
336 :
337 1188 : static const char *FetchSrcMethod(const CPLStringList &aosTO,
338 : const char *pszDefault = nullptr)
339 : {
340 1188 : const char *pszMethod = aosTO.FetchNameValue("SRC_METHOD");
341 1188 : if (!pszMethod)
342 1154 : pszMethod = aosTO.FetchNameValueDef("METHOD", pszDefault);
343 1188 : return pszMethod;
344 : }
345 :
346 : /************************************************************************/
347 : /* GetSrcDSProjection() */
348 : /* */
349 : /* Takes into account SRC_SRS transformer option in priority, and then */
350 : /* dataset characteristics as well as the METHOD transformer */
351 : /* option to determine the source SRS. */
352 : /************************************************************************/
353 :
354 1332 : static CPLString GetSrcDSProjection(GDALDatasetH hDS, CSLConstList papszTO)
355 : {
356 1332 : const char *pszProjection = CSLFetchNameValue(papszTO, "SRC_SRS");
357 1332 : if (pszProjection != nullptr || hDS == nullptr)
358 : {
359 65 : return pszProjection ? pszProjection : "";
360 : }
361 :
362 1267 : const char *pszMethod = FetchSrcMethod(papszTO);
363 1267 : CSLConstList papszMD = nullptr;
364 1267 : const OGRSpatialReferenceH hSRS = GDALGetSpatialRef(hDS);
365 : const char *pszGeolocationDataset =
366 1267 : CSLFetchNameValueDef(papszTO, "SRC_GEOLOC_ARRAY",
367 : CSLFetchNameValue(papszTO, "GEOLOC_ARRAY"));
368 1267 : if (pszGeolocationDataset != nullptr &&
369 2 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")))
370 : {
371 : auto aosMD =
372 3 : GDALCreateGeolocationMetadata(hDS, pszGeolocationDataset, true);
373 3 : pszProjection = aosMD.FetchNameValue("SRS");
374 3 : if (pszProjection)
375 3 : return pszProjection; // return in this scope so that aosMD is
376 : // still valid
377 : }
378 1264 : else if (hSRS && (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")))
379 : {
380 990 : char *pszWKT = nullptr;
381 : {
382 1980 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
383 990 : if (OSRExportToWkt(hSRS, &pszWKT) != OGRERR_NONE)
384 : {
385 0 : CPLFree(pszWKT);
386 0 : pszWKT = nullptr;
387 0 : const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
388 0 : OSRExportToWktEx(hSRS, &pszWKT, apszOptions);
389 : }
390 : }
391 1980 : CPLString osWKT = pszWKT ? pszWKT : "";
392 990 : CPLFree(pszWKT);
393 990 : return osWKT;
394 : }
395 274 : else if (GDALGetGCPProjection(hDS) != nullptr &&
396 274 : strlen(GDALGetGCPProjection(hDS)) > 0 &&
397 553 : GDALGetGCPCount(hDS) > 1 &&
398 5 : (pszMethod == nullptr || STARTS_WITH_CI(pszMethod, "GCP_")))
399 : {
400 27 : pszProjection = GDALGetGCPProjection(hDS);
401 : }
402 250 : else if (GDALGetMetadata(hDS, GDAL_MDD_RPC) != nullptr &&
403 3 : (pszMethod == nullptr || EQUAL(pszMethod, GDAL_MDD_RPC)))
404 : {
405 14 : pszProjection = SRS_WKT_WGS84_LAT_LONG;
406 : }
407 233 : else if ((papszMD = GDALGetMetadata(hDS, GDAL_MDD_GEOLOCATION)) !=
408 246 : nullptr &&
409 13 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOLOC_ARRAY")))
410 : {
411 22 : pszProjection = CSLFetchNameValue(papszMD, "SRS");
412 : }
413 274 : return pszProjection ? pszProjection : "";
414 : }
415 :
416 : /************************************************************************/
417 : /* CreateCTCutlineToSrc() */
418 : /************************************************************************/
419 :
420 58 : static std::unique_ptr<OGRCoordinateTransformation> CreateCTCutlineToSrc(
421 : const OGRSpatialReference *poRasterSRS, const OGRSpatialReference *poDstSRS,
422 : const OGRSpatialReference *poCutlineSRS, CSLConstList papszTO)
423 : {
424 58 : const OGRSpatialReference *poCutlineOrTargetSRS =
425 58 : poCutlineSRS ? poCutlineSRS : poDstSRS;
426 58 : std::unique_ptr<OGRCoordinateTransformation> poCTCutlineToSrc;
427 112 : if (poCutlineOrTargetSRS && poRasterSRS &&
428 54 : !poCutlineOrTargetSRS->IsSame(poRasterSRS))
429 : {
430 16 : OGRCoordinateTransformationOptions oOptions;
431 : // If the cutline SRS is the same as the target SRS and there is
432 : // an explicit -ct between the source SRS and the target SRS, then
433 : // use it in the reverse way to transform from the cutline SRS to
434 : // the source SRS.
435 8 : if (poDstSRS && poCutlineOrTargetSRS->IsSame(poDstSRS))
436 : {
437 : const char *pszCT =
438 2 : CSLFetchNameValue(papszTO, "COORDINATE_OPERATION");
439 2 : if (pszCT)
440 : {
441 1 : oOptions.SetCoordinateOperation(pszCT, /* bInverse = */ true);
442 : }
443 : }
444 8 : poCTCutlineToSrc.reset(OGRCreateCoordinateTransformation(
445 : poCutlineOrTargetSRS, poRasterSRS, oOptions));
446 : }
447 58 : return poCTCutlineToSrc;
448 : }
449 :
450 : /************************************************************************/
451 : /* CropToCutline() */
452 : /************************************************************************/
453 :
454 18 : static CPLErr CropToCutline(const OGRGeometry *poCutline, CSLConstList papszTO,
455 : CSLConstList papszWarpOptions, int nSrcCount,
456 : GDALDatasetH *pahSrcDS, double &dfMinX,
457 : double &dfMinY, double &dfMaxX, double &dfMaxY,
458 : const GDALWarpAppOptions *psOptions)
459 : {
460 : // We could possibly directly reproject from cutline SRS to target SRS,
461 : // but when applying the cutline, it is reprojected to source raster image
462 : // space using the source SRS. To be consistent, we reproject
463 : // the cutline from cutline SRS to source SRS and then from source SRS to
464 : // target SRS.
465 18 : const OGRSpatialReference *poCutlineSRS = poCutline->getSpatialReference();
466 18 : const char *pszThisTargetSRS = CSLFetchNameValue(papszTO, "DST_SRS");
467 18 : std::unique_ptr<OGRSpatialReference> poSrcSRS;
468 18 : std::unique_ptr<OGRSpatialReference> poDstSRS;
469 :
470 : const CPLString osThisSourceSRS =
471 36 : GetSrcDSProjection(nSrcCount > 0 ? pahSrcDS[0] : nullptr, papszTO);
472 18 : if (!osThisSourceSRS.empty())
473 : {
474 14 : poSrcSRS = std::make_unique<OGRSpatialReference>();
475 14 : poSrcSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
476 14 : if (poSrcSRS->SetFromUserInput(osThisSourceSRS) != OGRERR_NONE)
477 : {
478 1 : CPLError(CE_Failure, CPLE_AppDefined,
479 : "Cannot compute bounding box of cutline.");
480 1 : return CE_Failure;
481 : }
482 : }
483 4 : else if (!pszThisTargetSRS && !poCutlineSRS)
484 : {
485 1 : OGREnvelope sEnvelope;
486 1 : poCutline->getEnvelope(&sEnvelope);
487 :
488 1 : dfMinX = sEnvelope.MinX;
489 1 : dfMinY = sEnvelope.MinY;
490 1 : dfMaxX = sEnvelope.MaxX;
491 1 : dfMaxY = sEnvelope.MaxY;
492 :
493 1 : return CE_None;
494 : }
495 : else
496 : {
497 3 : CPLError(CE_Failure, CPLE_AppDefined,
498 : "Cannot compute bounding box of cutline. Cannot find "
499 : "source SRS");
500 3 : return CE_Failure;
501 : }
502 :
503 13 : if (pszThisTargetSRS)
504 : {
505 3 : poDstSRS = std::make_unique<OGRSpatialReference>();
506 3 : poDstSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
507 3 : if (poDstSRS->SetFromUserInput(pszThisTargetSRS) != OGRERR_NONE)
508 : {
509 1 : CPLError(CE_Failure, CPLE_AppDefined,
510 : "Cannot compute bounding box of cutline.");
511 1 : return CE_Failure;
512 : }
513 : }
514 : else
515 : {
516 10 : poDstSRS.reset(poSrcSRS->Clone());
517 : }
518 :
519 24 : auto poCutlineGeom = std::unique_ptr<OGRGeometry>(poCutline->clone());
520 12 : auto poCTCutlineToSrc = CreateCTCutlineToSrc(poSrcSRS.get(), poDstSRS.get(),
521 24 : poCutlineSRS, papszTO);
522 :
523 12 : std::unique_ptr<OGRCoordinateTransformation> poCTSrcToDst;
524 12 : if (!poSrcSRS->IsSame(poDstSRS.get()))
525 : {
526 1 : poCTSrcToDst.reset(
527 1 : OGRCreateCoordinateTransformation(poSrcSRS.get(), poDstSRS.get()));
528 : }
529 :
530 : // Reproject cutline to target SRS, by doing intermediate vertex
531 : // densification in source SRS.
532 12 : if (poCTSrcToDst || poCTCutlineToSrc)
533 : {
534 2 : OGREnvelope sLastEnvelope, sCurEnvelope;
535 0 : std::unique_ptr<OGRGeometry> poTransformedGeom;
536 : auto poGeomInSrcSRS =
537 2 : std::unique_ptr<OGRGeometry>(poCutlineGeom->clone());
538 2 : if (poCTCutlineToSrc)
539 : {
540 4 : poGeomInSrcSRS.reset(OGRGeometryFactory::transformWithOptions(
541 2 : poGeomInSrcSRS.get(), poCTCutlineToSrc.get(), nullptr));
542 2 : if (!poGeomInSrcSRS)
543 0 : return CE_Failure;
544 : }
545 :
546 : // Do not use a smaller epsilon, otherwise it could cause useless
547 : // segmentization (https://github.com/OSGeo/gdal/issues/4826)
548 2 : constexpr double epsilon = 1e-10;
549 3 : for (int nIter = 0; nIter < 10; nIter++)
550 : {
551 3 : poTransformedGeom.reset(poGeomInSrcSRS->clone());
552 3 : if (poCTSrcToDst)
553 : {
554 4 : poTransformedGeom.reset(
555 : OGRGeometryFactory::transformWithOptions(
556 2 : poTransformedGeom.get(), poCTSrcToDst.get(), nullptr));
557 2 : if (!poTransformedGeom)
558 0 : return CE_Failure;
559 : }
560 3 : poTransformedGeom->getEnvelope(&sCurEnvelope);
561 3 : if (nIter > 0 || !poCTSrcToDst)
562 : {
563 2 : if (std::abs(sCurEnvelope.MinX - sLastEnvelope.MinX) <=
564 2 : epsilon *
565 2 : std::abs(sCurEnvelope.MinX + sLastEnvelope.MinX) &&
566 2 : std::abs(sCurEnvelope.MinY - sLastEnvelope.MinY) <=
567 2 : epsilon *
568 2 : std::abs(sCurEnvelope.MinY + sLastEnvelope.MinY) &&
569 2 : std::abs(sCurEnvelope.MaxX - sLastEnvelope.MaxX) <=
570 2 : epsilon *
571 6 : std::abs(sCurEnvelope.MaxX + sLastEnvelope.MaxX) &&
572 2 : std::abs(sCurEnvelope.MaxY - sLastEnvelope.MaxY) <=
573 2 : epsilon *
574 2 : std::abs(sCurEnvelope.MaxY + sLastEnvelope.MaxY))
575 : {
576 2 : break;
577 : }
578 : }
579 : double dfAverageSegmentLength =
580 1 : GetAverageSegmentLength(poGeomInSrcSRS.get());
581 1 : poGeomInSrcSRS->segmentize(dfAverageSegmentLength / 4);
582 :
583 1 : sLastEnvelope = sCurEnvelope;
584 : }
585 :
586 2 : poCutlineGeom = std::move(poTransformedGeom);
587 : }
588 :
589 12 : OGREnvelope sEnvelope;
590 12 : poCutlineGeom->getEnvelope(&sEnvelope);
591 :
592 12 : dfMinX = sEnvelope.MinX;
593 12 : dfMinY = sEnvelope.MinY;
594 12 : dfMaxX = sEnvelope.MaxX;
595 12 : dfMaxY = sEnvelope.MaxY;
596 22 : if (!poCTSrcToDst && nSrcCount > 0 && psOptions->dfXRes == 0.0 &&
597 10 : psOptions->dfYRes == 0.0)
598 : {
599 : // No raster reprojection: stick on exact pixel boundaries of the source
600 : // to preserve resolution and avoid resampling
601 : double adfGT[6];
602 10 : if (GDALGetGeoTransform(pahSrcDS[0], adfGT) == CE_None)
603 : {
604 : // We allow for a relative error in coordinates up to 0.1% of the
605 : // pixel size for rounding purposes.
606 10 : constexpr double REL_EPS_PIXEL = 1e-3;
607 10 : if (CPLFetchBool(papszWarpOptions, "CUTLINE_ALL_TOUCHED", false))
608 : {
609 : // All touched ? Then make the extent a bit larger than the
610 : // cutline envelope
611 3 : dfMinX = adfGT[0] +
612 3 : floor((dfMinX - adfGT[0]) / adfGT[1] + REL_EPS_PIXEL) *
613 3 : adfGT[1];
614 3 : dfMinY = adfGT[3] +
615 3 : ceil((dfMinY - adfGT[3]) / adfGT[5] - REL_EPS_PIXEL) *
616 3 : adfGT[5];
617 3 : dfMaxX = adfGT[0] +
618 3 : ceil((dfMaxX - adfGT[0]) / adfGT[1] - REL_EPS_PIXEL) *
619 3 : adfGT[1];
620 3 : dfMaxY = adfGT[3] +
621 3 : floor((dfMaxY - adfGT[3]) / adfGT[5] + REL_EPS_PIXEL) *
622 3 : adfGT[5];
623 : }
624 : else
625 : {
626 : // Otherwise, make it a bit smaller
627 7 : dfMinX = adfGT[0] +
628 7 : ceil((dfMinX - adfGT[0]) / adfGT[1] - REL_EPS_PIXEL) *
629 7 : adfGT[1];
630 7 : dfMinY = adfGT[3] +
631 7 : floor((dfMinY - adfGT[3]) / adfGT[5] + REL_EPS_PIXEL) *
632 7 : adfGT[5];
633 7 : dfMaxX = adfGT[0] +
634 7 : floor((dfMaxX - adfGT[0]) / adfGT[1] + REL_EPS_PIXEL) *
635 7 : adfGT[1];
636 7 : dfMaxY = adfGT[3] +
637 7 : ceil((dfMaxY - adfGT[3]) / adfGT[5] - REL_EPS_PIXEL) *
638 7 : adfGT[5];
639 : }
640 : }
641 : }
642 :
643 12 : return CE_None;
644 : }
645 :
646 2322 : static bool MustApplyVerticalShift(GDALDatasetH hWrkSrcDS,
647 : const GDALWarpAppOptions *psOptions,
648 : OGRSpatialReference &oSRSSrc,
649 : OGRSpatialReference &oSRSDst,
650 : bool &bSrcHasVertAxis, bool &bDstHasVertAxis)
651 : {
652 2322 : bool bApplyVShift = psOptions->bVShift;
653 :
654 : // Check if we must do vertical shift grid transform
655 : const char *pszSrcWKT =
656 2322 : psOptions->aosTransformerOptions.FetchNameValue("SRC_SRS");
657 2322 : if (pszSrcWKT)
658 62 : oSRSSrc.SetFromUserInput(pszSrcWKT);
659 : else
660 : {
661 2260 : auto hSRS = GDALGetSpatialRef(hWrkSrcDS);
662 2260 : if (hSRS)
663 1648 : oSRSSrc = *(OGRSpatialReference::FromHandle(hSRS));
664 : else
665 612 : return false;
666 : }
667 :
668 : const char *pszDstWKT =
669 1710 : psOptions->aosTransformerOptions.FetchNameValue("DST_SRS");
670 1710 : if (pszDstWKT)
671 549 : oSRSDst.SetFromUserInput(pszDstWKT);
672 : else
673 1161 : return false;
674 :
675 549 : if (oSRSSrc.IsSame(&oSRSDst))
676 20 : return false;
677 :
678 1028 : bSrcHasVertAxis = oSRSSrc.IsCompound() ||
679 499 : ((oSRSSrc.IsProjected() || oSRSSrc.IsGeographic()) &&
680 499 : oSRSSrc.GetAxesCount() == 3);
681 :
682 1036 : bDstHasVertAxis = oSRSDst.IsCompound() ||
683 507 : ((oSRSDst.IsProjected() || oSRSDst.IsGeographic()) &&
684 506 : oSRSDst.GetAxesCount() == 3);
685 :
686 885 : if ((GDALGetRasterCount(hWrkSrcDS) == 1 || psOptions->bVShift) &&
687 356 : (bSrcHasVertAxis || bDstHasVertAxis))
688 : {
689 40 : bApplyVShift = true;
690 : }
691 529 : return bApplyVShift;
692 : }
693 :
694 : /************************************************************************/
695 : /* ApplyVerticalShift() */
696 : /************************************************************************/
697 :
698 1157 : static bool ApplyVerticalShift(GDALDatasetH hWrkSrcDS,
699 : const GDALWarpAppOptions *psOptions,
700 : GDALWarpOptions *psWO)
701 : {
702 1157 : if (psOptions->bVShift)
703 : {
704 0 : psWO->papszWarpOptions = CSLSetNameValue(psWO->papszWarpOptions,
705 : "APPLY_VERTICAL_SHIFT", "YES");
706 : }
707 :
708 2314 : OGRSpatialReference oSRSSrc;
709 1157 : OGRSpatialReference oSRSDst;
710 1157 : bool bSrcHasVertAxis = false;
711 1157 : bool bDstHasVertAxis = false;
712 : bool bApplyVShift =
713 1157 : MustApplyVerticalShift(hWrkSrcDS, psOptions, oSRSSrc, oSRSDst,
714 : bSrcHasVertAxis, bDstHasVertAxis);
715 :
716 2018 : if ((GDALGetRasterCount(hWrkSrcDS) == 1 || psOptions->bVShift) &&
717 861 : (bSrcHasVertAxis || bDstHasVertAxis))
718 : {
719 20 : bApplyVShift = true;
720 20 : psWO->papszWarpOptions = CSLSetNameValue(psWO->papszWarpOptions,
721 : "APPLY_VERTICAL_SHIFT", "YES");
722 :
723 20 : if (CSLFetchNameValue(psWO->papszWarpOptions,
724 20 : "MULT_FACTOR_VERTICAL_SHIFT") == nullptr)
725 : {
726 : // Select how to go from input dataset units to meters
727 20 : double dfToMeterSrc = 1.0;
728 : const char *pszUnit =
729 20 : GDALGetRasterUnitType(GDALGetRasterBand(hWrkSrcDS, 1));
730 :
731 20 : double dfToMeterSrcAxis = 1.0;
732 20 : if (bSrcHasVertAxis)
733 : {
734 16 : oSRSSrc.GetAxis(nullptr, 2, nullptr, &dfToMeterSrcAxis);
735 : }
736 :
737 20 : if (pszUnit && (EQUAL(pszUnit, "m") || EQUAL(pszUnit, "meter") ||
738 19 : EQUAL(pszUnit, "metre")))
739 : {
740 : }
741 19 : else if (pszUnit &&
742 19 : (EQUAL(pszUnit, "ft") || EQUAL(pszUnit, "foot")))
743 : {
744 1 : dfToMeterSrc = CPLAtof(SRS_UL_FOOT_CONV);
745 : }
746 18 : else if (pszUnit && (EQUAL(pszUnit, "US survey foot")))
747 : {
748 2 : dfToMeterSrc = CPLAtof(SRS_UL_US_FOOT_CONV);
749 : }
750 16 : else if (pszUnit && !EQUAL(pszUnit, ""))
751 : {
752 1 : if (bSrcHasVertAxis)
753 : {
754 1 : dfToMeterSrc = dfToMeterSrcAxis;
755 : }
756 : else
757 : {
758 0 : CPLError(CE_Warning, CPLE_AppDefined,
759 : "Unknown units=%s. Assuming metre.", pszUnit);
760 : }
761 : }
762 : else
763 : {
764 15 : if (bSrcHasVertAxis)
765 11 : oSRSSrc.GetAxis(nullptr, 2, nullptr, &dfToMeterSrc);
766 : }
767 :
768 20 : double dfToMeterDst = 1.0;
769 20 : if (bDstHasVertAxis)
770 20 : oSRSDst.GetAxis(nullptr, 2, nullptr, &dfToMeterDst);
771 :
772 20 : if (dfToMeterSrc > 0 && dfToMeterDst > 0)
773 : {
774 20 : const double dfMultFactorVerticalShift =
775 20 : dfToMeterSrc / dfToMeterDst;
776 20 : CPLDebug("WARP", "Applying MULT_FACTOR_VERTICAL_SHIFT=%.18g",
777 : dfMultFactorVerticalShift);
778 20 : psWO->papszWarpOptions = CSLSetNameValue(
779 : psWO->papszWarpOptions, "MULT_FACTOR_VERTICAL_SHIFT",
780 : CPLSPrintf("%.18g", dfMultFactorVerticalShift));
781 :
782 20 : const double dfMultFactorVerticalShiftPipeline =
783 20 : dfToMeterSrcAxis / dfToMeterDst;
784 20 : CPLDebug("WARP",
785 : "Applying MULT_FACTOR_VERTICAL_SHIFT_PIPELINE=%.18g",
786 : dfMultFactorVerticalShiftPipeline);
787 20 : psWO->papszWarpOptions = CSLSetNameValue(
788 : psWO->papszWarpOptions,
789 : "MULT_FACTOR_VERTICAL_SHIFT_PIPELINE",
790 : CPLSPrintf("%.18g", dfMultFactorVerticalShiftPipeline));
791 : }
792 : }
793 : }
794 :
795 2314 : return bApplyVShift;
796 : }
797 :
798 : /************************************************************************/
799 : /* CanUseBuildVRT() */
800 : /************************************************************************/
801 :
802 5 : static bool CanUseBuildVRT(int nSrcCount, GDALDatasetH *pahSrcDS)
803 : {
804 :
805 5 : bool bCanUseBuildVRT = true;
806 10 : std::vector<std::array<double, 4>> aoExtents;
807 5 : bool bSrcHasAlpha = false;
808 5 : int nPrevBandCount = 0;
809 5 : OGRSpatialReference oSRSPrev;
810 5 : double dfLastResX = 0;
811 5 : double dfLastResY = 0;
812 14 : for (int i = 0; i < nSrcCount; i++)
813 : {
814 : double adfGT[6];
815 9 : auto hSrcDS = pahSrcDS[i];
816 9 : if (EQUAL(GDALGetDescription(hSrcDS), ""))
817 : {
818 0 : bCanUseBuildVRT = false;
819 0 : break;
820 : }
821 18 : if (GDALGetGeoTransform(hSrcDS, adfGT) != CE_None || adfGT[2] != 0 ||
822 18 : adfGT[4] != 0 || adfGT[5] > 0)
823 : {
824 0 : bCanUseBuildVRT = false;
825 0 : break;
826 : }
827 9 : const double dfMinX = adfGT[0];
828 9 : const double dfMinY = adfGT[3] + GDALGetRasterYSize(hSrcDS) * adfGT[5];
829 9 : const double dfMaxX = adfGT[0] + GDALGetRasterXSize(hSrcDS) * adfGT[1];
830 9 : const double dfMaxY = adfGT[3];
831 9 : const int nBands = GDALGetRasterCount(hSrcDS);
832 9 : if (nBands > 1 && GDALGetRasterColorInterpretation(GDALGetRasterBand(
833 : hSrcDS, nBands)) == GCI_AlphaBand)
834 : {
835 4 : bSrcHasAlpha = true;
836 : }
837 : aoExtents.emplace_back(
838 9 : std::array<double, 4>{{dfMinX, dfMinY, dfMaxX, dfMaxY}});
839 9 : const auto poSRS = GDALDataset::FromHandle(hSrcDS)->GetSpatialRef();
840 9 : if (i == 0)
841 : {
842 5 : nPrevBandCount = nBands;
843 5 : if (poSRS)
844 5 : oSRSPrev = *poSRS;
845 5 : dfLastResX = adfGT[1];
846 5 : dfLastResY = adfGT[5];
847 : }
848 : else
849 : {
850 4 : if (nPrevBandCount != nBands)
851 : {
852 0 : bCanUseBuildVRT = false;
853 0 : break;
854 : }
855 4 : if (poSRS == nullptr && !oSRSPrev.IsEmpty())
856 : {
857 0 : bCanUseBuildVRT = false;
858 0 : break;
859 : }
860 8 : if (poSRS != nullptr &&
861 4 : (oSRSPrev.IsEmpty() || !poSRS->IsSame(&oSRSPrev)))
862 : {
863 0 : bCanUseBuildVRT = false;
864 0 : break;
865 : }
866 4 : if (dfLastResX != adfGT[1] || dfLastResY != adfGT[5])
867 : {
868 0 : bCanUseBuildVRT = false;
869 0 : break;
870 : }
871 : }
872 : }
873 5 : if (bSrcHasAlpha && bCanUseBuildVRT)
874 : {
875 : // Quadratic performance loop. If that happens to be an issue,
876 : // we might need to build a quad tree
877 2 : for (size_t i = 0; i < aoExtents.size(); i++)
878 : {
879 2 : const double dfMinX = aoExtents[i][0];
880 2 : const double dfMinY = aoExtents[i][1];
881 2 : const double dfMaxX = aoExtents[i][2];
882 2 : const double dfMaxY = aoExtents[i][3];
883 2 : for (size_t j = i + 1; j < aoExtents.size(); j++)
884 : {
885 2 : const double dfOtherMinX = aoExtents[j][0];
886 2 : const double dfOtherMinY = aoExtents[j][1];
887 2 : const double dfOtherMaxX = aoExtents[j][2];
888 2 : const double dfOtherMaxY = aoExtents[j][3];
889 2 : if (dfMinX < dfOtherMaxX && dfOtherMinX < dfMaxX &&
890 2 : dfMinY < dfOtherMaxY && dfOtherMinY < dfMaxY)
891 : {
892 2 : bCanUseBuildVRT = false;
893 2 : break;
894 : }
895 : }
896 2 : if (!bCanUseBuildVRT)
897 2 : break;
898 : }
899 : }
900 10 : return bCanUseBuildVRT;
901 : }
902 :
903 : #ifdef HAVE_TIFF
904 :
905 : /************************************************************************/
906 : /* DealWithCOGOptions() */
907 : /************************************************************************/
908 :
909 8 : static bool DealWithCOGOptions(CPLStringList &aosCreateOptions, int nSrcCount,
910 : GDALDatasetH *pahSrcDS,
911 : GDALWarpAppOptions *psOptions,
912 : GDALTransformerArgUniquePtr &hUniqueTransformArg)
913 : {
914 10 : const auto SetDstSRS = [psOptions](const std::string &osTargetSRS)
915 : {
916 : const char *pszExistingDstSRS =
917 5 : psOptions->aosTransformerOptions.FetchNameValue("DST_SRS");
918 5 : if (pszExistingDstSRS)
919 : {
920 2 : OGRSpatialReference oSRS1;
921 2 : oSRS1.SetFromUserInput(pszExistingDstSRS);
922 2 : OGRSpatialReference oSRS2;
923 2 : oSRS2.SetFromUserInput(osTargetSRS.c_str());
924 2 : if (!oSRS1.IsSame(&oSRS2))
925 : {
926 4 : const char *pszOutputArg = psOptions->bInvokedFromGdalAlgorithm
927 2 : ? "--output-crs"
928 : : "-t_srs";
929 :
930 2 : CPLError(CE_Failure, CPLE_AppDefined,
931 : "Target SRS implied by COG creation options is not "
932 : "the same as the one specified by %s",
933 : pszOutputArg);
934 2 : return false;
935 : }
936 : }
937 : psOptions->aosTransformerOptions.SetNameValue("DST_SRS",
938 3 : osTargetSRS.c_str());
939 3 : return true;
940 8 : };
941 :
942 16 : CPLString osTargetSRS;
943 8 : if (COGGetTargetSRS(aosCreateOptions.List(), osTargetSRS))
944 : {
945 5 : if (!SetDstSRS(osTargetSRS))
946 2 : return false;
947 : }
948 :
949 6 : if (!(psOptions->dfMinX == 0 && psOptions->dfMinY == 0 &&
950 5 : psOptions->dfMaxX == 0 && psOptions->dfMaxY == 0 &&
951 5 : psOptions->dfXRes == 0 && psOptions->dfYRes == 0 &&
952 5 : psOptions->nForcePixels == 0 && psOptions->nForceLines == 0))
953 : {
954 2 : if (!psOptions->bResampleAlgSpecifiedByUser && nSrcCount > 0)
955 : {
956 : try
957 : {
958 1 : GDALGetWarpResampleAlg(
959 2 : COGGetResampling(GDALDataset::FromHandle(pahSrcDS[0]),
960 1 : aosCreateOptions.List())
961 : .c_str(),
962 1 : psOptions->eResampleAlg);
963 : }
964 0 : catch (const std::invalid_argument &)
965 : {
966 : // Cannot happen actually. Coverity Scan false positive...
967 0 : CPLAssert(false);
968 : }
969 : }
970 2 : return true;
971 : }
972 :
973 8 : GDALWarpAppOptions oClonedOptions(*psOptions);
974 4 : oClonedOptions.bQuiet = true;
975 : const CPLString osTmpFilename(
976 8 : VSIMemGenerateHiddenFilename("gdalwarp_tmp.tif"));
977 8 : CPLStringList aosTmpGTiffCreateOptions;
978 4 : aosTmpGTiffCreateOptions.SetNameValue("SPARSE_OK", "YES");
979 4 : aosTmpGTiffCreateOptions.SetNameValue("TILED", "YES");
980 4 : aosTmpGTiffCreateOptions.SetNameValue("BLOCKXSIZE", "4096");
981 4 : aosTmpGTiffCreateOptions.SetNameValue("BLOCKYSIZE", "4096");
982 4 : auto hTmpDS = GDALWarpCreateOutput(
983 : nSrcCount, pahSrcDS, osTmpFilename, "GTiff",
984 : oClonedOptions.aosTransformerOptions.List(),
985 4 : aosTmpGTiffCreateOptions.List(), oClonedOptions.eOutputType,
986 : hUniqueTransformArg, false, &oClonedOptions,
987 : /* bUpdateTransformerWithDestGT = */ false);
988 4 : if (hTmpDS == nullptr)
989 : {
990 0 : return false;
991 : }
992 :
993 4 : CPLString osResampling;
994 4 : int nXSize = 0;
995 4 : int nYSize = 0;
996 4 : double dfMinX = 0;
997 4 : double dfMinY = 0;
998 4 : double dfMaxX = 0;
999 4 : double dfMaxY = 0;
1000 4 : bool bRet = true;
1001 4 : if (COGGetWarpingCharacteristics(GDALDataset::FromHandle(hTmpDS),
1002 4 : aosCreateOptions.List(), osResampling,
1003 : osTargetSRS, nXSize, nYSize, dfMinX,
1004 : dfMinY, dfMaxX, dfMaxY))
1005 : {
1006 2 : if (!psOptions->bResampleAlgSpecifiedByUser)
1007 : {
1008 : try
1009 : {
1010 2 : GDALGetWarpResampleAlg(osResampling, psOptions->eResampleAlg);
1011 : }
1012 0 : catch (const std::invalid_argument &)
1013 : {
1014 : // Cannot happen actually. Coverity Scan false positive...
1015 0 : CPLAssert(false);
1016 : }
1017 : }
1018 :
1019 2 : psOptions->dfMinX = dfMinX;
1020 2 : psOptions->dfMinY = dfMinY;
1021 2 : psOptions->dfMaxX = dfMaxX;
1022 2 : psOptions->dfMaxY = dfMaxY;
1023 2 : psOptions->nForcePixels = nXSize;
1024 2 : psOptions->nForceLines = nYSize;
1025 2 : COGRemoveWarpingOptions(aosCreateOptions);
1026 : }
1027 4 : GDALClose(hTmpDS);
1028 4 : VSIUnlink(osTmpFilename);
1029 4 : return bRet;
1030 : }
1031 :
1032 : #endif
1033 :
1034 : /************************************************************************/
1035 : /* GDALWarpIndirect() */
1036 : /************************************************************************/
1037 :
1038 : static GDALDatasetH
1039 : GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
1040 : GDALDatasetH *pahSrcDS,
1041 : GDALTransformerArgUniquePtr hUniqueTransformArg,
1042 : GDALWarpAppOptions *psOptions, int *pbUsageError);
1043 :
1044 1028 : static int CPL_STDCALL myScaledProgress(double dfProgress, const char *,
1045 : void *pProgressData)
1046 : {
1047 1028 : return GDALScaledProgress(dfProgress, nullptr, pProgressData);
1048 : }
1049 :
1050 17 : static GDALDatasetH GDALWarpIndirect(const char *pszDest, GDALDriverH hDriver,
1051 : int nSrcCount, GDALDatasetH *pahSrcDS,
1052 : GDALWarpAppOptions *psOptions,
1053 : int *pbUsageError)
1054 : {
1055 34 : CPLStringList aosCreateOptions(psOptions->aosCreateOptions);
1056 17 : psOptions->aosCreateOptions.Clear();
1057 :
1058 : // Do not use a warped VRT input for COG output, because that would cause
1059 : // warping to be done both during overview computation and creation of
1060 : // full resolution image. Better materialize a temporary GTiff a bit later
1061 : // in that method.
1062 17 : if (nSrcCount == 1 && !EQUAL(psOptions->osFormat.c_str(), "COG"))
1063 : {
1064 6 : psOptions->osFormat = "VRT";
1065 6 : auto pfnProgress = psOptions->pfnProgress;
1066 6 : psOptions->pfnProgress = GDALDummyProgress;
1067 6 : auto pProgressData = psOptions->pProgressData;
1068 6 : psOptions->pProgressData = nullptr;
1069 :
1070 6 : auto hTmpDS = GDALWarpDirect("", nullptr, nSrcCount, pahSrcDS, nullptr,
1071 : psOptions, pbUsageError);
1072 6 : if (hTmpDS)
1073 : {
1074 6 : auto hRet = GDALCreateCopy(hDriver, pszDest, hTmpDS, FALSE,
1075 6 : aosCreateOptions.List(), pfnProgress,
1076 : pProgressData);
1077 6 : GDALClose(hTmpDS);
1078 6 : return hRet;
1079 : }
1080 0 : return nullptr;
1081 : }
1082 :
1083 : // Detect a pure mosaicing situation where a BuildVRT approach is
1084 : // sufficient.
1085 11 : GDALDatasetH hTmpDS = nullptr;
1086 11 : if (psOptions->aosTransformerOptions.empty() &&
1087 5 : psOptions->eOutputType == GDT_Unknown && psOptions->dfMinX == 0 &&
1088 5 : psOptions->dfMinY == 0 && psOptions->dfMaxX == 0 &&
1089 5 : psOptions->dfMaxY == 0 && psOptions->dfXRes == 0 &&
1090 5 : psOptions->dfYRes == 0 && psOptions->nForcePixels == 0 &&
1091 10 : psOptions->nForceLines == 0 &&
1092 21 : psOptions->osCutlineDSNameOrWKT.empty() &&
1093 5 : CanUseBuildVRT(nSrcCount, pahSrcDS))
1094 : {
1095 6 : CPLStringList aosArgv;
1096 3 : const int nBands = GDALGetRasterCount(pahSrcDS[0]);
1097 0 : if ((nBands == 1 ||
1098 0 : (nBands > 1 && GDALGetRasterColorInterpretation(GDALGetRasterBand(
1099 6 : pahSrcDS[0], nBands)) != GCI_AlphaBand)) &&
1100 3 : (psOptions->bEnableDstAlpha
1101 : #ifdef HAVE_TIFF
1102 3 : || (EQUAL(psOptions->osFormat.c_str(), "COG") &&
1103 3 : COGHasWarpingOptions(aosCreateOptions.List()) &&
1104 2 : CPLTestBool(
1105 : aosCreateOptions.FetchNameValueDef("ADD_ALPHA", "YES")))
1106 : #endif
1107 : ))
1108 : {
1109 2 : aosArgv.AddString("-addalpha");
1110 : }
1111 : auto psBuildVRTOptions =
1112 3 : GDALBuildVRTOptionsNew(aosArgv.List(), nullptr);
1113 3 : hTmpDS = GDALBuildVRT("", nSrcCount, pahSrcDS, nullptr,
1114 : psBuildVRTOptions, nullptr);
1115 3 : GDALBuildVRTOptionsFree(psBuildVRTOptions);
1116 : }
1117 11 : auto pfnProgress = psOptions->pfnProgress;
1118 11 : auto pProgressData = psOptions->pProgressData;
1119 22 : CPLString osTmpFilename;
1120 11 : double dfStartPctCreateCopy = 0.0;
1121 11 : if (hTmpDS == nullptr)
1122 : {
1123 0 : GDALTransformerArgUniquePtr hUniqueTransformArg;
1124 : #ifdef HAVE_TIFF
1125 : // Special processing for COG output. As some of its options do
1126 : // on-the-fly reprojection, take them into account now, and remove them
1127 : // from the COG creation stage.
1128 16 : if (EQUAL(psOptions->osFormat.c_str(), "COG") &&
1129 8 : !DealWithCOGOptions(aosCreateOptions, nSrcCount, pahSrcDS,
1130 : psOptions, hUniqueTransformArg))
1131 : {
1132 2 : return nullptr;
1133 : }
1134 : #endif
1135 :
1136 : // Materialize a temporary GeoTIFF with the result of the warp
1137 6 : psOptions->osFormat = "GTiff";
1138 6 : psOptions->aosCreateOptions.AddString("SPARSE_OK=YES");
1139 6 : psOptions->aosCreateOptions.AddString("COMPRESS=LZW");
1140 6 : psOptions->aosCreateOptions.AddString("TILED=YES");
1141 6 : psOptions->aosCreateOptions.AddString("BIGTIFF=YES");
1142 6 : psOptions->pfnProgress = myScaledProgress;
1143 6 : dfStartPctCreateCopy = 2. / 3;
1144 6 : psOptions->pProgressData = GDALCreateScaledProgress(
1145 : 0, dfStartPctCreateCopy, pfnProgress, pProgressData);
1146 6 : psOptions->bDeleteOutputFileOnceCreated = true;
1147 6 : osTmpFilename = CPLGenerateTempFilenameSafe(CPLGetFilename(pszDest));
1148 6 : hTmpDS = GDALWarpDirect(osTmpFilename, nullptr, nSrcCount, pahSrcDS,
1149 6 : std::move(hUniqueTransformArg), psOptions,
1150 : pbUsageError);
1151 6 : GDALDestroyScaledProgress(psOptions->pProgressData);
1152 6 : psOptions->pfnProgress = nullptr;
1153 6 : psOptions->pProgressData = nullptr;
1154 : }
1155 9 : if (hTmpDS)
1156 : {
1157 9 : auto pScaledProgressData = GDALCreateScaledProgress(
1158 : dfStartPctCreateCopy, 1.0, pfnProgress, pProgressData);
1159 9 : auto hRet = GDALCreateCopy(hDriver, pszDest, hTmpDS, FALSE,
1160 9 : aosCreateOptions.List(), myScaledProgress,
1161 : pScaledProgressData);
1162 9 : GDALDestroyScaledProgress(pScaledProgressData);
1163 9 : GDALClose(hTmpDS);
1164 : VSIStatBufL sStat;
1165 15 : if (!osTmpFilename.empty() &&
1166 6 : VSIStatL(osTmpFilename.c_str(), &sStat) == 0)
1167 : {
1168 6 : GDALDeleteDataset(GDALGetDriverByName("GTiff"), osTmpFilename);
1169 : }
1170 9 : return hRet;
1171 : }
1172 0 : return nullptr;
1173 : }
1174 :
1175 : /************************************************************************/
1176 : /* GDALWarp() */
1177 : /************************************************************************/
1178 :
1179 : /**
1180 : * Image reprojection and warping function.
1181 : *
1182 : * This is the equivalent of the <a href="/programs/gdalwarp.html">gdalwarp</a>
1183 : * utility.
1184 : *
1185 : * GDALWarpAppOptions* must be allocated and freed with GDALWarpAppOptionsNew()
1186 : * and GDALWarpAppOptionsFree() respectively.
1187 : * pszDest and hDstDS cannot be used at the same time.
1188 : *
1189 : * @param pszDest the destination dataset path or NULL.
1190 : * @param hDstDS the destination dataset or NULL.
1191 : * @param nSrcCount the number of input datasets.
1192 : * @param pahSrcDS the list of input datasets. For practical purposes, the type
1193 : * of this argument should be considered as "const GDALDatasetH* const*", that
1194 : * is neither the array nor its values are mutated by this function.
1195 : * @param psOptionsIn the options struct returned by GDALWarpAppOptionsNew() or
1196 : * NULL.
1197 : * @param pbUsageError pointer to a integer output variable to store if any
1198 : * usage error has occurred, or NULL.
1199 : * @return the output dataset (new dataset that must be closed using
1200 : * GDALClose(), or hDstDS if not NULL) or NULL in case of error. If the output
1201 : * format is a VRT dataset, then the returned VRT dataset has a reference to
1202 : * pahSrcDS[0]. Hence pahSrcDS[0] should be closed after the returned dataset
1203 : * if using GDALClose().
1204 : * A safer alternative is to use GDALReleaseDataset() instead of using
1205 : * GDALClose(), in which case you can close datasets in any order.
1206 : *
1207 : * @since GDAL 2.1
1208 : */
1209 :
1210 1177 : GDALDatasetH GDALWarp(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
1211 : GDALDatasetH *pahSrcDS,
1212 : const GDALWarpAppOptions *psOptionsIn, int *pbUsageError)
1213 : {
1214 1177 : CPLErrorReset();
1215 :
1216 2377 : for (int i = 0; i < nSrcCount; i++)
1217 : {
1218 1200 : if (!pahSrcDS[i])
1219 0 : return nullptr;
1220 : }
1221 :
1222 2354 : GDALWarpAppOptions oOptionsTmp;
1223 1177 : if (psOptionsIn)
1224 1175 : oOptionsTmp = *psOptionsIn;
1225 1177 : GDALWarpAppOptions *psOptions = &oOptionsTmp;
1226 :
1227 1177 : if (hDstDS == nullptr)
1228 : {
1229 1084 : if (psOptions->osFormat.empty())
1230 : {
1231 419 : psOptions->osFormat = GetOutputDriverForRaster(pszDest);
1232 419 : if (psOptions->osFormat.empty())
1233 : {
1234 0 : return nullptr;
1235 : }
1236 : }
1237 :
1238 1084 : auto hDriver = GDALGetDriverByName(psOptions->osFormat.c_str());
1239 2168 : if (hDriver != nullptr &&
1240 1084 : (EQUAL(psOptions->osFormat.c_str(), "COG") ||
1241 1073 : (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) ==
1242 6 : nullptr &&
1243 6 : GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) !=
1244 : nullptr)))
1245 : {
1246 17 : auto ret = GDALWarpIndirect(pszDest, hDriver, nSrcCount, pahSrcDS,
1247 : psOptions, pbUsageError);
1248 17 : return ret;
1249 : }
1250 : }
1251 :
1252 1160 : auto ret = GDALWarpDirect(pszDest, hDstDS, nSrcCount, pahSrcDS, nullptr,
1253 : psOptions, pbUsageError);
1254 :
1255 1160 : return ret;
1256 : }
1257 :
1258 : /************************************************************************/
1259 : /* UseTEAndTSAndTRConsistently() */
1260 : /************************************************************************/
1261 :
1262 1086 : static bool UseTEAndTSAndTRConsistently(const GDALWarpAppOptions *psOptions)
1263 : {
1264 : // We normally don't allow -te, -ts and -tr together, unless they are all
1265 : // consistent. The interest of this is to use the -tr values to produce
1266 : // exact pixel size, rather than inferring it from -te and -ts
1267 :
1268 : // Constant and logic to be kept in sync with cogdriver.cpp
1269 1086 : constexpr double RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP = 1e-8;
1270 199 : return psOptions->nForcePixels != 0 && psOptions->nForceLines != 0 &&
1271 194 : psOptions->dfXRes != 0 && psOptions->dfYRes != 0 &&
1272 52 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1273 0 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0) &&
1274 52 : fabs((psOptions->dfMaxX - psOptions->dfMinX) / psOptions->dfXRes -
1275 52 : psOptions->nForcePixels) <=
1276 1285 : RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP &&
1277 52 : fabs((psOptions->dfMaxY - psOptions->dfMinY) / psOptions->dfYRes -
1278 52 : psOptions->nForceLines) <=
1279 1086 : RELATIVE_ERROR_RES_SHARED_BY_COG_AND_GDALWARP;
1280 : }
1281 :
1282 : /************************************************************************/
1283 : /* CheckOptions() */
1284 : /************************************************************************/
1285 :
1286 1172 : static bool CheckOptions(const char *pszDest, GDALDatasetH hDstDS,
1287 : int nSrcCount, GDALDatasetH *pahSrcDS,
1288 : GDALWarpAppOptions *psOptions, bool &bVRT,
1289 : int *pbUsageError)
1290 : {
1291 :
1292 1172 : if (hDstDS)
1293 : {
1294 93 : if (psOptions->bCreateOutput == true)
1295 : {
1296 0 : CPLError(CE_Warning, CPLE_AppDefined,
1297 : "All options related to creation ignored in update mode");
1298 0 : psOptions->bCreateOutput = false;
1299 : }
1300 : }
1301 :
1302 2530 : if ((psOptions->osFormat.empty() &&
1303 2344 : EQUAL(CPLGetExtensionSafe(pszDest).c_str(), "VRT")) ||
1304 1172 : (EQUAL(psOptions->osFormat.c_str(), "VRT")))
1305 : {
1306 182 : if (hDstDS != nullptr)
1307 : {
1308 0 : CPLError(CE_Warning, CPLE_NotSupported,
1309 : "VRT output not compatible with existing dataset.");
1310 0 : return false;
1311 : }
1312 :
1313 182 : bVRT = true;
1314 :
1315 182 : if (nSrcCount > 1)
1316 : {
1317 0 : CPLError(CE_Warning, CPLE_AppDefined,
1318 : "gdalwarp -of VRT just takes into account "
1319 : "the first source dataset.\nIf all source datasets "
1320 : "are in the same projection, try making a mosaic of\n"
1321 : "them with gdalbuildvrt, and use the resulting "
1322 : "VRT file as the input of\ngdalwarp -of VRT.");
1323 : }
1324 : }
1325 :
1326 : /* -------------------------------------------------------------------- */
1327 : /* Check that incompatible options are not used */
1328 : /* -------------------------------------------------------------------- */
1329 :
1330 999 : if ((psOptions->nForcePixels != 0 || psOptions->nForceLines != 0) &&
1331 2197 : (psOptions->dfXRes != 0 && psOptions->dfYRes != 0) &&
1332 26 : !UseTEAndTSAndTRConsistently(psOptions))
1333 : {
1334 0 : CPLError(CE_Failure, CPLE_IllegalArg,
1335 : "-tr and -ts options cannot be used at the same time.");
1336 0 : if (pbUsageError)
1337 0 : *pbUsageError = TRUE;
1338 0 : return false;
1339 : }
1340 :
1341 1172 : if (psOptions->bTargetAlignedPixels && psOptions->dfXRes == 0 &&
1342 1 : psOptions->dfYRes == 0)
1343 : {
1344 1 : CPLError(CE_Failure, CPLE_IllegalArg,
1345 : "-tap option cannot be used without using -tr.");
1346 1 : if (pbUsageError)
1347 1 : *pbUsageError = TRUE;
1348 1 : return false;
1349 : }
1350 :
1351 1171 : if (!psOptions->bQuiet &&
1352 81 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1353 64 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0))
1354 : {
1355 17 : if (psOptions->dfMinX >= psOptions->dfMaxX)
1356 0 : CPLError(CE_Warning, CPLE_AppDefined,
1357 : "-te values have minx >= maxx. This will result in a "
1358 : "horizontally flipped image.");
1359 17 : if (psOptions->dfMinY >= psOptions->dfMaxY)
1360 0 : CPLError(CE_Warning, CPLE_AppDefined,
1361 : "-te values have miny >= maxy. This will result in a "
1362 : "vertically flipped image.");
1363 : }
1364 :
1365 1171 : if (psOptions->dfErrorThreshold < 0)
1366 : {
1367 : // By default, use approximate transformer unless RPC_DEM is specified
1368 1158 : if (psOptions->aosTransformerOptions.FetchNameValue("RPC_DEM") !=
1369 : nullptr)
1370 4 : psOptions->dfErrorThreshold = 0.0;
1371 : else
1372 1154 : psOptions->dfErrorThreshold = 0.125;
1373 : }
1374 :
1375 : /* -------------------------------------------------------------------- */
1376 : /* -te_srs option */
1377 : /* -------------------------------------------------------------------- */
1378 1171 : if (!psOptions->osTE_SRS.empty())
1379 : {
1380 6 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
1381 0 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
1382 : {
1383 0 : CPLError(CE_Warning, CPLE_AppDefined,
1384 : "-te_srs ignored since -te is not specified.");
1385 : }
1386 : else
1387 : {
1388 6 : OGRSpatialReference oSRSIn;
1389 6 : oSRSIn.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1390 6 : oSRSIn.SetFromUserInput(psOptions->osTE_SRS.c_str());
1391 6 : OGRSpatialReference oSRSDS;
1392 6 : oSRSDS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1393 6 : bool bOK = false;
1394 6 : if (psOptions->aosTransformerOptions.FetchNameValue("DST_SRS") !=
1395 : nullptr)
1396 : {
1397 3 : oSRSDS.SetFromUserInput(
1398 : psOptions->aosTransformerOptions.FetchNameValue("DST_SRS"));
1399 3 : bOK = true;
1400 : }
1401 3 : else if (psOptions->aosTransformerOptions.FetchNameValue(
1402 3 : "SRC_SRS") != nullptr)
1403 : {
1404 0 : oSRSDS.SetFromUserInput(
1405 : psOptions->aosTransformerOptions.FetchNameValue("SRC_SRS"));
1406 0 : bOK = true;
1407 : }
1408 3 : else if (nSrcCount)
1409 : {
1410 : const auto poSrcSRS =
1411 3 : GDALDataset::FromHandle(pahSrcDS[0])->GetSpatialRef();
1412 3 : if (poSrcSRS)
1413 : {
1414 3 : oSRSDS = *poSrcSRS;
1415 3 : bOK = true;
1416 : }
1417 : }
1418 6 : if (!bOK)
1419 : {
1420 0 : CPLError(CE_Failure, CPLE_AppDefined,
1421 : "-te_srs ignored since none of -t_srs, -s_srs is "
1422 : "specified or the input dataset has no projection.");
1423 0 : return false;
1424 : }
1425 6 : if (!oSRSIn.IsSame(&oSRSDS))
1426 : {
1427 5 : double dfWestLongitudeDeg = 0.0;
1428 5 : double dfSouthLatitudeDeg = 0.0;
1429 5 : double dfEastLongitudeDeg = 0.0;
1430 5 : double dfNorthLatitudeDeg = 0.0;
1431 :
1432 5 : OGRCoordinateTransformationOptions options;
1433 5 : if (GDALComputeAreaOfInterest(
1434 : &oSRSIn, psOptions->dfMinX, psOptions->dfMinY,
1435 : psOptions->dfMaxX, psOptions->dfMaxY,
1436 : dfWestLongitudeDeg, dfSouthLatitudeDeg,
1437 : dfEastLongitudeDeg, dfNorthLatitudeDeg))
1438 : {
1439 5 : options.SetAreaOfInterest(
1440 : dfWestLongitudeDeg, dfSouthLatitudeDeg,
1441 : dfEastLongitudeDeg, dfNorthLatitudeDeg);
1442 : }
1443 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
1444 : OGRCreateCoordinateTransformation(&oSRSIn, &oSRSDS,
1445 5 : options));
1446 5 : constexpr int DENSIFY_PTS = 21;
1447 10 : if (!(poCT && poCT->TransformBounds(
1448 : psOptions->dfMinX, psOptions->dfMinY,
1449 : psOptions->dfMaxX, psOptions->dfMaxY,
1450 : &(psOptions->dfMinX), &(psOptions->dfMinY),
1451 : &(psOptions->dfMaxX), &(psOptions->dfMaxY),
1452 10 : DENSIFY_PTS)))
1453 : {
1454 0 : CPLError(CE_Failure, CPLE_AppDefined,
1455 : "-te_srs ignored since coordinate transformation "
1456 : "failed.");
1457 0 : return false;
1458 : }
1459 : }
1460 : }
1461 : }
1462 1171 : return true;
1463 : }
1464 :
1465 : /************************************************************************/
1466 : /* ProcessCutlineOptions() */
1467 : /************************************************************************/
1468 :
1469 1171 : static bool ProcessCutlineOptions(int nSrcCount, GDALDatasetH *pahSrcDS,
1470 : GDALWarpAppOptions *psOptions,
1471 : std::unique_ptr<OGRGeometry> &poCutline)
1472 : {
1473 1171 : if (!psOptions->osCutlineDSNameOrWKT.empty())
1474 : {
1475 : CPLErr eError;
1476 58 : OGRGeometryH hCutline = nullptr;
1477 116 : eError = LoadCutline(psOptions->osCutlineDSNameOrWKT,
1478 58 : psOptions->osCutlineSRS, psOptions->osCLayer,
1479 58 : psOptions->osCWHERE, psOptions->osCSQL, &hCutline);
1480 58 : poCutline.reset(OGRGeometry::FromHandle(hCutline));
1481 58 : if (eError == CE_Failure)
1482 : {
1483 6 : return false;
1484 : }
1485 : }
1486 :
1487 1165 : if (psOptions->bCropToCutline && poCutline)
1488 : {
1489 : CPLErr eError;
1490 18 : eError = CropToCutline(poCutline.get(),
1491 18 : psOptions->aosTransformerOptions.List(),
1492 18 : psOptions->aosWarpOptions.List(), nSrcCount,
1493 18 : pahSrcDS, psOptions->dfMinX, psOptions->dfMinY,
1494 18 : psOptions->dfMaxX, psOptions->dfMaxY, psOptions);
1495 18 : if (eError == CE_Failure)
1496 : {
1497 5 : return false;
1498 : }
1499 : }
1500 :
1501 : const char *pszWarpThreads =
1502 1160 : psOptions->aosWarpOptions.FetchNameValue("NUM_THREADS");
1503 1160 : if (pszWarpThreads != nullptr)
1504 : {
1505 : /* Used by TPS transformer to parallelize direct and inverse matrix
1506 : * computation */
1507 : psOptions->aosTransformerOptions.SetNameValue("NUM_THREADS",
1508 28 : pszWarpThreads);
1509 : }
1510 :
1511 1160 : return true;
1512 : }
1513 :
1514 : /************************************************************************/
1515 : /* CreateOutput() */
1516 : /************************************************************************/
1517 :
1518 : static GDALDatasetH
1519 1067 : CreateOutput(const char *pszDest, int nSrcCount, GDALDatasetH *pahSrcDS,
1520 : GDALWarpAppOptions *psOptions, const bool bInitDestSetByUser,
1521 : GDALTransformerArgUniquePtr &hUniqueTransformArg)
1522 : {
1523 1067 : if (nSrcCount == 1 && !psOptions->bDisableSrcAlpha)
1524 : {
1525 2072 : if (GDALGetRasterCount(pahSrcDS[0]) > 0 &&
1526 1036 : GDALGetRasterColorInterpretation(GDALGetRasterBand(
1527 : pahSrcDS[0], GDALGetRasterCount(pahSrcDS[0]))) == GCI_AlphaBand)
1528 : {
1529 39 : psOptions->bEnableSrcAlpha = true;
1530 39 : psOptions->bEnableDstAlpha = true;
1531 39 : if (!psOptions->bQuiet)
1532 0 : printf("Using band %d of source image as alpha.\n",
1533 : GDALGetRasterCount(pahSrcDS[0]));
1534 : }
1535 : }
1536 :
1537 1067 : auto hDstDS = GDALWarpCreateOutput(
1538 : nSrcCount, pahSrcDS, pszDest, psOptions->osFormat.c_str(),
1539 : psOptions->aosTransformerOptions.List(),
1540 1067 : psOptions->aosCreateOptions.List(), psOptions->eOutputType,
1541 1067 : hUniqueTransformArg, psOptions->bSetColorInterpretation, psOptions,
1542 : /* bUpdateTransformerWithDestGT = */ true);
1543 1067 : if (hDstDS == nullptr)
1544 : {
1545 17 : return nullptr;
1546 : }
1547 1050 : psOptions->bCreateOutput = true;
1548 :
1549 1050 : if (!bInitDestSetByUser)
1550 : {
1551 1030 : if (psOptions->osDstNodata.empty())
1552 : {
1553 898 : psOptions->aosWarpOptions.SetNameValue("INIT_DEST", "0");
1554 : }
1555 : else
1556 : {
1557 132 : psOptions->aosWarpOptions.SetNameValue("INIT_DEST", "NO_DATA");
1558 : }
1559 : }
1560 :
1561 1050 : return hDstDS;
1562 : }
1563 :
1564 : /************************************************************************/
1565 : /* EditISIS3ForMetadataChanges() */
1566 : /************************************************************************/
1567 :
1568 : static std::string
1569 2 : EditISIS3ForMetadataChanges(const char *pszJSON,
1570 : const GDALWarpAppOptions *psOptions)
1571 : {
1572 4 : CPLJSONDocument oJSONDocument;
1573 2 : if (!oJSONDocument.LoadMemory(pszJSON))
1574 : {
1575 0 : return std::string();
1576 : }
1577 :
1578 4 : auto oRoot = oJSONDocument.GetRoot();
1579 2 : if (!oRoot.IsValid())
1580 : {
1581 0 : return std::string();
1582 : }
1583 :
1584 6 : auto oGDALHistory = oRoot.GetObj("GDALHistory");
1585 2 : if (!oGDALHistory.IsValid())
1586 : {
1587 2 : oGDALHistory = CPLJSONObject();
1588 2 : oRoot.Add("GDALHistory", oGDALHistory);
1589 : }
1590 2 : oGDALHistory["_type"] = "object";
1591 :
1592 2 : char szFullFilename[2048] = {0};
1593 2 : if (!CPLGetExecPath(szFullFilename, sizeof(szFullFilename) - 1))
1594 0 : strcpy(szFullFilename, "unknown_program");
1595 4 : const CPLString osProgram(CPLGetBasenameSafe(szFullFilename));
1596 4 : const CPLString osPath(CPLGetPathSafe(szFullFilename));
1597 :
1598 2 : oGDALHistory["GdalVersion"] = GDALVersionInfo("RELEASE_NAME");
1599 2 : oGDALHistory["Program"] = osProgram;
1600 2 : if (osPath != ".")
1601 2 : oGDALHistory["ProgramPath"] = osPath;
1602 :
1603 4 : std::string osArgs;
1604 6 : for (const char *pszArg : psOptions->aosArgs)
1605 : {
1606 4 : if (!osArgs.empty())
1607 2 : osArgs += ' ';
1608 4 : osArgs += pszArg;
1609 : }
1610 2 : oGDALHistory["ProgramArguments"] = osArgs;
1611 :
1612 2 : oGDALHistory.Add(
1613 : "Comment",
1614 : "Part of that metadata might be invalid due to a reprojection "
1615 : "operation having been performed by GDAL tools");
1616 :
1617 2 : return oRoot.Format(CPLJSONObject::PrettyFormat::Pretty);
1618 : }
1619 :
1620 : /************************************************************************/
1621 : /* ProcessMetadata() */
1622 : /************************************************************************/
1623 :
1624 1164 : static void ProcessMetadata(int iSrc, GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
1625 : const GDALWarpAppOptions *psOptions,
1626 : const bool bEnableDstAlpha,
1627 : bool bVerticalShiftApplied)
1628 : {
1629 1164 : if (psOptions->bCopyMetadata)
1630 : {
1631 1164 : const char *pszSrcInfo = nullptr;
1632 1164 : GDALRasterBandH hSrcBand = nullptr;
1633 1164 : GDALRasterBandH hDstBand = nullptr;
1634 :
1635 : /* copy metadata from first dataset */
1636 1164 : if (iSrc == 0)
1637 : {
1638 1140 : CPLDebug(
1639 : "WARP",
1640 : "Copying metadata from first source to destination dataset");
1641 : /* copy dataset-level metadata */
1642 1140 : CSLConstList papszMetadata = GDALGetMetadata(hSrcDS, nullptr);
1643 :
1644 1140 : char **papszMetadataNew = nullptr;
1645 2954 : for (int i = 0;
1646 2954 : papszMetadata != nullptr && papszMetadata[i] != nullptr; i++)
1647 : {
1648 : // Do not preserve NODATA_VALUES when the output includes an
1649 : // alpha band
1650 1814 : if (bEnableDstAlpha &&
1651 198 : STARTS_WITH_CI(papszMetadata[i], "NODATA_VALUES="))
1652 : {
1653 1 : continue;
1654 : }
1655 : // Do not preserve the CACHE_PATH from the WMS driver
1656 1813 : if (STARTS_WITH_CI(papszMetadata[i], "CACHE_PATH="))
1657 : {
1658 0 : continue;
1659 : }
1660 :
1661 : papszMetadataNew =
1662 1813 : CSLAddString(papszMetadataNew, papszMetadata[i]);
1663 : }
1664 :
1665 1140 : if (CSLCount(papszMetadataNew) > 0)
1666 : {
1667 761 : if (GDALSetMetadata(hDstDS, papszMetadataNew, nullptr) !=
1668 : CE_None)
1669 0 : CPLError(CE_Warning, CPLE_AppDefined,
1670 : "error copying metadata to destination dataset.");
1671 : }
1672 :
1673 1140 : CSLDestroy(papszMetadataNew);
1674 :
1675 : /* ISIS3 -> ISIS3 special case */
1676 1140 : if (EQUAL(psOptions->osFormat.c_str(), "ISIS3") ||
1677 1139 : EQUAL(psOptions->osFormat.c_str(), "PDS4") ||
1678 2966 : EQUAL(psOptions->osFormat.c_str(), "GTIFF") ||
1679 687 : EQUAL(psOptions->osFormat.c_str(), "COG"))
1680 : {
1681 : CSLConstList papszMD_ISIS3 =
1682 453 : GDALGetMetadata(hSrcDS, "json:ISIS3");
1683 453 : if (papszMD_ISIS3 != nullptr && papszMD_ISIS3[0])
1684 : {
1685 4 : std::string osJSON = papszMD_ISIS3[0];
1686 : osJSON =
1687 2 : EditISIS3ForMetadataChanges(osJSON.c_str(), psOptions);
1688 2 : if (!osJSON.empty())
1689 : {
1690 2 : char *apszMD[] = {osJSON.data(), nullptr};
1691 2 : GDALSetMetadata(hDstDS, apszMD, "json:ISIS3");
1692 : }
1693 : }
1694 : }
1695 687 : else if (EQUAL(psOptions->osFormat.c_str(), "PDS4"))
1696 : {
1697 0 : CSLConstList papszMD_PDS4 = GDALGetMetadata(hSrcDS, "xml:PDS4");
1698 0 : if (papszMD_PDS4 != nullptr)
1699 0 : GDALSetMetadata(hDstDS, papszMD_PDS4, "xml:PDS4");
1700 : }
1701 687 : else if (EQUAL(psOptions->osFormat.c_str(), "VICAR"))
1702 : {
1703 : CSLConstList papszMD_VICAR =
1704 0 : GDALGetMetadata(hSrcDS, "json:VICAR");
1705 0 : if (papszMD_VICAR != nullptr)
1706 0 : GDALSetMetadata(hDstDS, papszMD_VICAR, "json:VICAR");
1707 : }
1708 :
1709 : /* copy band-level metadata and other info */
1710 1140 : if (GDALGetRasterCount(hSrcDS) == GDALGetRasterCount(hDstDS))
1711 : {
1712 2468 : for (int iBand = 0; iBand < GDALGetRasterCount(hSrcDS); iBand++)
1713 : {
1714 1411 : hSrcBand = GDALGetRasterBand(hSrcDS, iBand + 1);
1715 1411 : hDstBand = GDALGetRasterBand(hDstDS, iBand + 1);
1716 : /* copy metadata, except stats (#5319) */
1717 1411 : papszMetadata = GDALGetMetadata(hSrcBand, nullptr);
1718 1411 : if (CSLCount(papszMetadata) > 0)
1719 : {
1720 : // GDALSetMetadata( hDstBand, papszMetadata, NULL );
1721 22 : papszMetadataNew = nullptr;
1722 120 : for (int i = 0; papszMetadata != nullptr &&
1723 120 : papszMetadata[i] != nullptr;
1724 : i++)
1725 : {
1726 98 : if (!STARTS_WITH(papszMetadata[i], "STATISTICS_"))
1727 83 : papszMetadataNew = CSLAddString(
1728 83 : papszMetadataNew, papszMetadata[i]);
1729 : }
1730 22 : GDALSetMetadata(hDstBand, papszMetadataNew, nullptr);
1731 22 : CSLDestroy(papszMetadataNew);
1732 : }
1733 : /* copy other info (Description, Unit Type) - what else? */
1734 1411 : if (psOptions->bCopyBandInfo && !bVerticalShiftApplied)
1735 : {
1736 1391 : pszSrcInfo = GDALGetDescription(hSrcBand);
1737 1391 : if (pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0)
1738 3 : GDALSetDescription(hDstBand, pszSrcInfo);
1739 1391 : pszSrcInfo = GDALGetRasterUnitType(hSrcBand);
1740 1391 : if (pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0)
1741 34 : GDALSetRasterUnitType(hDstBand, pszSrcInfo);
1742 : }
1743 : }
1744 : }
1745 : }
1746 : /* remove metadata that conflicts between datasets */
1747 : else
1748 : {
1749 24 : CPLDebug("WARP",
1750 : "Removing conflicting metadata from destination dataset "
1751 : "(source #%d)",
1752 : iSrc);
1753 : /* remove conflicting dataset-level metadata */
1754 24 : RemoveConflictingMetadata(hDstDS, GDALGetMetadata(hSrcDS, nullptr),
1755 : psOptions->osMDConflictValue.c_str());
1756 :
1757 : /* remove conflicting copy band-level metadata and other info */
1758 24 : if (GDALGetRasterCount(hSrcDS) == GDALGetRasterCount(hDstDS))
1759 : {
1760 44 : for (int iBand = 0; iBand < GDALGetRasterCount(hSrcDS); iBand++)
1761 : {
1762 23 : hSrcBand = GDALGetRasterBand(hSrcDS, iBand + 1);
1763 23 : hDstBand = GDALGetRasterBand(hDstDS, iBand + 1);
1764 : /* remove conflicting metadata */
1765 23 : RemoveConflictingMetadata(
1766 : hDstBand, GDALGetMetadata(hSrcBand, nullptr),
1767 : psOptions->osMDConflictValue.c_str());
1768 : /* remove conflicting info */
1769 23 : if (psOptions->bCopyBandInfo)
1770 : {
1771 23 : pszSrcInfo = GDALGetDescription(hSrcBand);
1772 23 : const char *pszDstInfo = GDALGetDescription(hDstBand);
1773 23 : if (!(pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0 &&
1774 0 : pszDstInfo != nullptr && strlen(pszDstInfo) > 0 &&
1775 0 : EQUAL(pszSrcInfo, pszDstInfo)))
1776 23 : GDALSetDescription(hDstBand, "");
1777 23 : pszSrcInfo = GDALGetRasterUnitType(hSrcBand);
1778 23 : pszDstInfo = GDALGetRasterUnitType(hDstBand);
1779 23 : if (!(pszSrcInfo != nullptr && strlen(pszSrcInfo) > 0 &&
1780 0 : pszDstInfo != nullptr && strlen(pszDstInfo) > 0 &&
1781 0 : EQUAL(pszSrcInfo, pszDstInfo)))
1782 23 : GDALSetRasterUnitType(hDstBand, "");
1783 : }
1784 : }
1785 : }
1786 : }
1787 : }
1788 1164 : }
1789 :
1790 : /************************************************************************/
1791 : /* SetupNoData() */
1792 : /************************************************************************/
1793 :
1794 1161 : static CPLErr SetupNoData(const char *pszDest, int iSrc, GDALDatasetH hSrcDS,
1795 : GDALDatasetH hWrkSrcDS, GDALDatasetH hDstDS,
1796 : GDALWarpOptions *psWO, GDALWarpAppOptions *psOptions,
1797 : const bool bEnableDstAlpha,
1798 : const bool bInitDestSetByUser)
1799 : {
1800 1211 : if (!psOptions->osSrcNodata.empty() &&
1801 50 : !EQUAL(psOptions->osSrcNodata.c_str(), "none"))
1802 : {
1803 : CPLStringList aosTokens(
1804 46 : CSLTokenizeString(psOptions->osSrcNodata.c_str()));
1805 46 : const int nTokenCount = aosTokens.Count();
1806 :
1807 46 : psWO->padfSrcNoDataReal =
1808 46 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
1809 46 : psWO->padfSrcNoDataImag = nullptr;
1810 :
1811 123 : for (int i = 0; i < psWO->nBandCount; i++)
1812 : {
1813 78 : if (i < nTokenCount)
1814 : {
1815 : double dfNoDataReal;
1816 : double dfNoDataImag;
1817 :
1818 48 : if (CPLStringToComplex(aosTokens[i], &dfNoDataReal,
1819 48 : &dfNoDataImag) != CE_None)
1820 : {
1821 1 : CPLError(CE_Failure, CPLE_AppDefined,
1822 : "Error parsing srcnodata for band %d", i + 1);
1823 1 : return CE_Failure;
1824 : }
1825 :
1826 94 : psWO->padfSrcNoDataReal[i] =
1827 47 : GDALAdjustNoDataCloseToFloatMax(dfNoDataReal);
1828 :
1829 47 : if (strchr(aosTokens[i], 'i') != nullptr)
1830 : {
1831 1 : if (psWO->padfSrcNoDataImag == nullptr)
1832 : {
1833 1 : psWO->padfSrcNoDataImag = static_cast<double *>(
1834 1 : CPLCalloc(psWO->nBandCount, sizeof(double)));
1835 : }
1836 2 : psWO->padfSrcNoDataImag[i] =
1837 1 : GDALAdjustNoDataCloseToFloatMax(dfNoDataImag);
1838 : }
1839 : }
1840 : else
1841 : {
1842 30 : psWO->padfSrcNoDataReal[i] = psWO->padfSrcNoDataReal[i - 1];
1843 30 : if (psWO->padfSrcNoDataImag != nullptr)
1844 : {
1845 0 : psWO->padfSrcNoDataImag[i] = psWO->padfSrcNoDataImag[i - 1];
1846 : }
1847 : }
1848 : }
1849 :
1850 71 : if (psWO->nBandCount > 1 &&
1851 26 : CSLFetchNameValue(psWO->papszWarpOptions, "UNIFIED_SRC_NODATA") ==
1852 : nullptr)
1853 : {
1854 6 : CPLDebug("WARP", "Set UNIFIED_SRC_NODATA=YES");
1855 6 : psWO->papszWarpOptions = CSLSetNameValue(
1856 : psWO->papszWarpOptions, "UNIFIED_SRC_NODATA", "YES");
1857 : }
1858 : }
1859 :
1860 : /* -------------------------------------------------------------------- */
1861 : /* If -srcnodata was not specified, but the data has nodata */
1862 : /* values, use them. */
1863 : /* -------------------------------------------------------------------- */
1864 1160 : if (psOptions->osSrcNodata.empty())
1865 : {
1866 1111 : int bHaveNodata = FALSE;
1867 1111 : double dfReal = 0.0;
1868 :
1869 2533 : for (int i = 0; !bHaveNodata && i < psWO->nBandCount; i++)
1870 : {
1871 : GDALRasterBandH hBand =
1872 1422 : GDALGetRasterBand(hWrkSrcDS, psWO->panSrcBands[i]);
1873 1422 : dfReal = GDALGetRasterNoDataValue(hBand, &bHaveNodata);
1874 : }
1875 :
1876 1111 : if (bHaveNodata)
1877 : {
1878 96 : if (!psOptions->bQuiet)
1879 : {
1880 10 : if (std::isnan(dfReal))
1881 0 : printf("Using internal nodata values (e.g. nan) for image "
1882 : "%s.\n",
1883 : GDALGetDescription(hSrcDS));
1884 : else
1885 10 : printf("Using internal nodata values (e.g. %g) for image "
1886 : "%s.\n",
1887 : dfReal, GDALGetDescription(hSrcDS));
1888 : }
1889 96 : psWO->padfSrcNoDataReal = static_cast<double *>(
1890 96 : CPLMalloc(psWO->nBandCount * sizeof(double)));
1891 :
1892 229 : for (int i = 0; i < psWO->nBandCount; i++)
1893 : {
1894 : GDALRasterBandH hBand =
1895 133 : GDALGetRasterBand(hWrkSrcDS, psWO->panSrcBands[i]);
1896 :
1897 133 : dfReal = GDALGetRasterNoDataValue(hBand, &bHaveNodata);
1898 :
1899 133 : if (bHaveNodata)
1900 : {
1901 133 : psWO->padfSrcNoDataReal[i] = dfReal;
1902 : }
1903 : else
1904 : {
1905 0 : psWO->padfSrcNoDataReal[i] = -123456.789;
1906 : }
1907 : }
1908 : }
1909 : }
1910 :
1911 : /* -------------------------------------------------------------------- */
1912 : /* If the output dataset was created, and we have a destination */
1913 : /* nodata value, go through marking the bands with the information.*/
1914 : /* -------------------------------------------------------------------- */
1915 1304 : if (!psOptions->osDstNodata.empty() &&
1916 144 : !EQUAL(psOptions->osDstNodata.c_str(), "none"))
1917 : {
1918 : CPLStringList aosTokens(
1919 144 : CSLTokenizeString(psOptions->osDstNodata.c_str()));
1920 144 : const int nTokenCount = aosTokens.Count();
1921 144 : bool bDstNoDataNone = true;
1922 :
1923 144 : psWO->padfDstNoDataReal =
1924 144 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
1925 144 : psWO->padfDstNoDataImag =
1926 144 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
1927 :
1928 370 : for (int i = 0; i < psWO->nBandCount; i++)
1929 : {
1930 227 : psWO->padfDstNoDataReal[i] = -1.1e20;
1931 227 : psWO->padfDstNoDataImag[i] = 0.0;
1932 :
1933 227 : if (i < nTokenCount)
1934 : {
1935 145 : if (aosTokens[i] != nullptr && EQUAL(aosTokens[i], "none"))
1936 : {
1937 0 : CPLDebug("WARP", "dstnodata of band %d not set", i);
1938 0 : bDstNoDataNone = true;
1939 0 : continue;
1940 : }
1941 145 : else if (aosTokens[i] ==
1942 : nullptr) // this should not happen, but just in case
1943 : {
1944 0 : CPLError(CE_Failure, CPLE_AppDefined,
1945 : "Error parsing dstnodata arg #%d", i);
1946 0 : bDstNoDataNone = true;
1947 0 : continue;
1948 : }
1949 :
1950 145 : if (CPLStringToComplex(aosTokens[i],
1951 145 : psWO->padfDstNoDataReal + i,
1952 290 : psWO->padfDstNoDataImag + i) != CE_None)
1953 : {
1954 :
1955 1 : CPLError(CE_Failure, CPLE_AppDefined,
1956 : "Error parsing dstnodata for band %d", i + 1);
1957 1 : return CE_Failure;
1958 : }
1959 :
1960 288 : psWO->padfDstNoDataReal[i] =
1961 144 : GDALAdjustNoDataCloseToFloatMax(psWO->padfDstNoDataReal[i]);
1962 288 : psWO->padfDstNoDataImag[i] =
1963 144 : GDALAdjustNoDataCloseToFloatMax(psWO->padfDstNoDataImag[i]);
1964 144 : bDstNoDataNone = false;
1965 144 : CPLDebug("WARP", "dstnodata of band %d set to %f", i,
1966 144 : psWO->padfDstNoDataReal[i]);
1967 : }
1968 : else
1969 : {
1970 82 : if (!bDstNoDataNone)
1971 : {
1972 82 : psWO->padfDstNoDataReal[i] = psWO->padfDstNoDataReal[i - 1];
1973 82 : psWO->padfDstNoDataImag[i] = psWO->padfDstNoDataImag[i - 1];
1974 82 : CPLDebug("WARP",
1975 : "dstnodata of band %d set from previous band", i);
1976 : }
1977 : else
1978 : {
1979 0 : CPLDebug("WARP", "dstnodata value of band %d not set", i);
1980 0 : continue;
1981 : }
1982 : }
1983 :
1984 : GDALRasterBandH hBand =
1985 226 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
1986 226 : int bClamped = FALSE;
1987 226 : int bRounded = FALSE;
1988 226 : psWO->padfDstNoDataReal[i] = GDALAdjustValueToDataType(
1989 226 : GDALGetRasterDataType(hBand), psWO->padfDstNoDataReal[i],
1990 : &bClamped, &bRounded);
1991 :
1992 226 : if (bClamped)
1993 : {
1994 0 : CPLError(
1995 : CE_Warning, CPLE_AppDefined,
1996 : "for band %d, destination nodata value has been clamped "
1997 : "to %.0f, the original value being out of range.",
1998 0 : psWO->panDstBands[i], psWO->padfDstNoDataReal[i]);
1999 : }
2000 226 : else if (bRounded)
2001 : {
2002 0 : CPLError(
2003 : CE_Warning, CPLE_AppDefined,
2004 : "for band %d, destination nodata value has been rounded "
2005 : "to %.0f, %s being an integer datatype.",
2006 0 : psWO->panDstBands[i], psWO->padfDstNoDataReal[i],
2007 : GDALGetDataTypeName(GDALGetRasterDataType(hBand)));
2008 : }
2009 :
2010 226 : if (psOptions->bCreateOutput && iSrc == 0)
2011 : {
2012 222 : GDALSetRasterNoDataValue(
2013 222 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]),
2014 222 : psWO->padfDstNoDataReal[i]);
2015 : }
2016 : }
2017 : }
2018 :
2019 : /* check if the output dataset has already nodata */
2020 1159 : if (psOptions->osDstNodata.empty())
2021 : {
2022 1016 : int bHaveNodataAll = TRUE;
2023 2337 : for (int i = 0; i < psWO->nBandCount; i++)
2024 : {
2025 : GDALRasterBandH hBand =
2026 1321 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2027 1321 : int bHaveNodata = FALSE;
2028 1321 : GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2029 1321 : bHaveNodataAll &= bHaveNodata;
2030 : }
2031 1016 : if (bHaveNodataAll)
2032 : {
2033 6 : psWO->padfDstNoDataReal = static_cast<double *>(
2034 6 : CPLMalloc(psWO->nBandCount * sizeof(double)));
2035 13 : for (int i = 0; i < psWO->nBandCount; i++)
2036 : {
2037 : GDALRasterBandH hBand =
2038 7 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2039 7 : int bHaveNodata = FALSE;
2040 14 : psWO->padfDstNoDataReal[i] =
2041 7 : GDALGetRasterNoDataValue(hBand, &bHaveNodata);
2042 7 : CPLDebug("WARP", "band=%d dstNoData=%f", i,
2043 7 : psWO->padfDstNoDataReal[i]);
2044 : }
2045 : }
2046 : }
2047 :
2048 : // If creating a new file that has default nodata value,
2049 : // try to override the default output nodata values with the source ones.
2050 2175 : if (psOptions->osDstNodata.empty() && psWO->padfSrcNoDataReal != nullptr &&
2051 99 : psWO->padfDstNoDataReal != nullptr && psOptions->bCreateOutput &&
2052 2175 : iSrc == 0 && !bEnableDstAlpha)
2053 : {
2054 5 : for (int i = 0; i < psWO->nBandCount; i++)
2055 : {
2056 : GDALRasterBandH hBand =
2057 3 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]);
2058 3 : int bHaveNodata = FALSE;
2059 3 : CPLPushErrorHandler(CPLQuietErrorHandler);
2060 : bool bRedefinedOK =
2061 3 : (GDALSetRasterNoDataValue(hBand, psWO->padfSrcNoDataReal[i]) ==
2062 3 : CE_None &&
2063 3 : GDALGetRasterNoDataValue(hBand, &bHaveNodata) ==
2064 9 : psWO->padfSrcNoDataReal[i] &&
2065 3 : bHaveNodata);
2066 3 : CPLPopErrorHandler();
2067 3 : if (bRedefinedOK)
2068 : {
2069 3 : if (i == 0 && !psOptions->bQuiet)
2070 0 : printf("Copying nodata values from source %s "
2071 : "to destination %s.\n",
2072 : GDALGetDescription(hSrcDS), pszDest);
2073 3 : psWO->padfDstNoDataReal[i] = psWO->padfSrcNoDataReal[i];
2074 :
2075 3 : if (i == 0 && !bInitDestSetByUser)
2076 : {
2077 : /* As we didn't know at the beginning if there was source
2078 : * nodata */
2079 : /* we have initialized INIT_DEST=0. Override this with
2080 : * NO_DATA now */
2081 2 : psWO->papszWarpOptions = CSLSetNameValue(
2082 : psWO->papszWarpOptions, "INIT_DEST", "NO_DATA");
2083 : }
2084 : }
2085 : else
2086 : {
2087 0 : break;
2088 : }
2089 : }
2090 : }
2091 :
2092 : /* else try to fill dstNoData from source bands, unless -dstalpha is
2093 : * specified */
2094 1157 : else if (psOptions->osDstNodata.empty() &&
2095 1014 : psWO->padfSrcNoDataReal != nullptr &&
2096 2171 : psWO->padfDstNoDataReal == nullptr && !bEnableDstAlpha)
2097 : {
2098 82 : psWO->padfDstNoDataReal =
2099 82 : static_cast<double *>(CPLMalloc(psWO->nBandCount * sizeof(double)));
2100 :
2101 82 : if (psWO->padfSrcNoDataImag != nullptr)
2102 : {
2103 1 : psWO->padfDstNoDataImag = static_cast<double *>(
2104 1 : CPLMalloc(psWO->nBandCount * sizeof(double)));
2105 : }
2106 :
2107 82 : if (!psOptions->bQuiet)
2108 3 : printf("Copying nodata values from source %s to destination %s.\n",
2109 : GDALGetDescription(hSrcDS), pszDest);
2110 :
2111 183 : for (int i = 0; i < psWO->nBandCount; i++)
2112 : {
2113 101 : psWO->padfDstNoDataReal[i] = psWO->padfSrcNoDataReal[i];
2114 101 : if (psWO->padfSrcNoDataImag != nullptr)
2115 : {
2116 1 : psWO->padfDstNoDataImag[i] = psWO->padfSrcNoDataImag[i];
2117 : }
2118 101 : CPLDebug("WARP", "srcNoData=%f dstNoData=%f",
2119 101 : psWO->padfSrcNoDataReal[i], psWO->padfDstNoDataReal[i]);
2120 :
2121 101 : if (psOptions->bCreateOutput && iSrc == 0)
2122 : {
2123 101 : CPLDebug("WARP",
2124 : "calling GDALSetRasterNoDataValue() for band#%d", i);
2125 101 : GDALSetRasterNoDataValue(
2126 101 : GDALGetRasterBand(hDstDS, psWO->panDstBands[i]),
2127 101 : psWO->padfDstNoDataReal[i]);
2128 : }
2129 : }
2130 :
2131 82 : if (psOptions->bCreateOutput && !bInitDestSetByUser && iSrc == 0)
2132 : {
2133 : /* As we didn't know at the beginning if there was source nodata */
2134 : /* we have initialized INIT_DEST=0. Override this with NO_DATA now
2135 : */
2136 79 : psWO->papszWarpOptions =
2137 79 : CSLSetNameValue(psWO->papszWarpOptions, "INIT_DEST", "NO_DATA");
2138 : }
2139 : }
2140 :
2141 1159 : return CE_None;
2142 : }
2143 :
2144 : /************************************************************************/
2145 : /* SetupSkipNoSource() */
2146 : /************************************************************************/
2147 :
2148 1159 : static void SetupSkipNoSource(int iSrc, GDALDatasetH hDstDS,
2149 : GDALWarpOptions *psWO,
2150 : GDALWarpAppOptions *psOptions)
2151 : {
2152 1068 : if (psOptions->bCreateOutput && iSrc == 0 &&
2153 1046 : CSLFetchNameValue(psWO->papszWarpOptions, "SKIP_NOSOURCE") == nullptr &&
2154 1041 : CSLFetchNameValue(psWO->papszWarpOptions, "STREAMABLE_OUTPUT") ==
2155 2227 : nullptr &&
2156 : // This white list of drivers could potentially be extended.
2157 1040 : (EQUAL(psOptions->osFormat.c_str(), "MEM") ||
2158 634 : EQUAL(psOptions->osFormat.c_str(), "GTiff") ||
2159 188 : EQUAL(psOptions->osFormat.c_str(), "GPKG")))
2160 : {
2161 : // We can enable the optimization only if the user didn't specify
2162 : // a INIT_DEST value that would contradict the destination nodata.
2163 :
2164 852 : bool bOKRegardingInitDest = false;
2165 : const char *pszInitDest =
2166 852 : CSLFetchNameValue(psWO->papszWarpOptions, "INIT_DEST");
2167 852 : if (pszInitDest == nullptr || EQUAL(pszInitDest, "NO_DATA"))
2168 : {
2169 176 : bOKRegardingInitDest = true;
2170 :
2171 : // The MEM driver will return non-initialized blocks at 0
2172 : // so make sure that the nodata value is 0.
2173 176 : if (EQUAL(psOptions->osFormat.c_str(), "MEM"))
2174 : {
2175 347 : for (int i = 0; i < GDALGetRasterCount(hDstDS); i++)
2176 : {
2177 235 : int bHasNoData = false;
2178 235 : double dfDstNoDataVal = GDALGetRasterNoDataValue(
2179 : GDALGetRasterBand(hDstDS, i + 1), &bHasNoData);
2180 235 : if (bHasNoData && dfDstNoDataVal != 0.0)
2181 : {
2182 39 : bOKRegardingInitDest = false;
2183 39 : break;
2184 : }
2185 : }
2186 176 : }
2187 : }
2188 : else
2189 : {
2190 676 : char **papszTokensInitDest = CSLTokenizeString(pszInitDest);
2191 676 : const int nTokenCountInitDest = CSLCount(papszTokensInitDest);
2192 676 : if (nTokenCountInitDest == 1 ||
2193 0 : nTokenCountInitDest == GDALGetRasterCount(hDstDS))
2194 : {
2195 676 : bOKRegardingInitDest = true;
2196 1571 : for (int i = 0; i < GDALGetRasterCount(hDstDS); i++)
2197 : {
2198 903 : double dfInitVal = GDALAdjustNoDataCloseToFloatMax(
2199 903 : CPLAtofM(papszTokensInitDest[std::min(
2200 903 : i, nTokenCountInitDest - 1)]));
2201 903 : int bHasNoData = false;
2202 903 : double dfDstNoDataVal = GDALGetRasterNoDataValue(
2203 : GDALGetRasterBand(hDstDS, i + 1), &bHasNoData);
2204 903 : if (!((bHasNoData && dfInitVal == dfDstNoDataVal) ||
2205 901 : (!bHasNoData && dfInitVal == 0.0)))
2206 : {
2207 7 : bOKRegardingInitDest = false;
2208 8 : break;
2209 : }
2210 896 : if (EQUAL(psOptions->osFormat.c_str(), "MEM") &&
2211 896 : bHasNoData && dfDstNoDataVal != 0.0)
2212 : {
2213 1 : bOKRegardingInitDest = false;
2214 1 : break;
2215 : }
2216 : }
2217 : }
2218 676 : CSLDestroy(papszTokensInitDest);
2219 : }
2220 :
2221 852 : if (bOKRegardingInitDest)
2222 : {
2223 805 : CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2224 805 : psWO->papszWarpOptions =
2225 805 : CSLSetNameValue(psWO->papszWarpOptions, "SKIP_NOSOURCE", "YES");
2226 : }
2227 : }
2228 1159 : }
2229 :
2230 : /************************************************************************/
2231 : /* AdjustOutputExtentForRPC() */
2232 : /************************************************************************/
2233 :
2234 : /** Returns false if there's no intersection between source extent defined
2235 : * by RPC and target extent.
2236 : */
2237 1159 : static bool AdjustOutputExtentForRPC(GDALDatasetH hSrcDS, GDALDatasetH hDstDS,
2238 : GDALTransformerFunc pfnTransformer,
2239 : void *hTransformArg, GDALWarpOptions *psWO,
2240 : GDALWarpAppOptions *psOptions,
2241 : int &nWarpDstXOff, int &nWarpDstYOff,
2242 : int &nWarpDstXSize, int &nWarpDstYSize)
2243 : {
2244 1159 : if (CPLTestBool(CSLFetchNameValueDef(psWO->papszWarpOptions,
2245 921 : "SKIP_NOSOURCE", "NO")) &&
2246 921 : GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) != nullptr &&
2247 12 : EQUAL(FetchSrcMethod(psOptions->aosTransformerOptions, GDAL_MDD_RPC),
2248 2080 : GDAL_MDD_RPC) &&
2249 12 : CPLTestBool(
2250 : CPLGetConfigOption("RESTRICT_OUTPUT_DATASET_UPDATE", "YES")))
2251 : {
2252 : double adfSuggestedGeoTransform[6];
2253 : double adfExtent[4];
2254 : int nPixels, nLines;
2255 10 : if (GDALSuggestedWarpOutput2(hSrcDS, pfnTransformer, hTransformArg,
2256 : adfSuggestedGeoTransform, &nPixels,
2257 10 : &nLines, adfExtent, 0) == CE_None)
2258 : {
2259 6 : const double dfMinX = adfExtent[0];
2260 6 : const double dfMinY = adfExtent[1];
2261 6 : const double dfMaxX = adfExtent[2];
2262 6 : const double dfMaxY = adfExtent[3];
2263 6 : const double dfThreshold = static_cast<double>(INT_MAX) / 2;
2264 6 : if (std::fabs(dfMinX) < dfThreshold &&
2265 6 : std::fabs(dfMinY) < dfThreshold &&
2266 6 : std::fabs(dfMaxX) < dfThreshold &&
2267 6 : std::fabs(dfMaxY) < dfThreshold)
2268 : {
2269 6 : const int nPadding = 5;
2270 6 : nWarpDstXOff =
2271 6 : std::max(nWarpDstXOff,
2272 6 : static_cast<int>(std::floor(dfMinX)) - nPadding);
2273 6 : nWarpDstYOff =
2274 6 : std::max(nWarpDstYOff,
2275 6 : static_cast<int>(std::floor(dfMinY)) - nPadding);
2276 12 : nWarpDstXSize = std::min(nWarpDstXSize - nWarpDstXOff,
2277 6 : static_cast<int>(std::ceil(dfMaxX)) +
2278 6 : nPadding - nWarpDstXOff);
2279 12 : nWarpDstYSize = std::min(nWarpDstYSize - nWarpDstYOff,
2280 6 : static_cast<int>(std::ceil(dfMaxY)) +
2281 6 : nPadding - nWarpDstYOff);
2282 6 : if (nWarpDstXSize <= 0 || nWarpDstYSize <= 0)
2283 : {
2284 1 : CPLDebug("WARP",
2285 : "No intersection between source extent defined "
2286 : "by RPC and target extent");
2287 1 : return false;
2288 : }
2289 2 : if (nWarpDstXOff != 0 || nWarpDstYOff != 0 ||
2290 9 : nWarpDstXSize != GDALGetRasterXSize(hDstDS) ||
2291 2 : nWarpDstYSize != GDALGetRasterYSize(hDstDS))
2292 : {
2293 3 : CPLDebug("WARP",
2294 : "Restricting warping to output dataset window "
2295 : "%d,%d,%dx%d",
2296 : nWarpDstXOff, nWarpDstYOff, nWarpDstXSize,
2297 : nWarpDstYSize);
2298 : }
2299 : }
2300 : }
2301 : }
2302 1158 : return true;
2303 : }
2304 :
2305 : /************************************************************************/
2306 : /* GDALWarpDirect() */
2307 : /************************************************************************/
2308 :
2309 : static GDALDatasetH
2310 1172 : GDALWarpDirect(const char *pszDest, GDALDatasetH hDstDS, int nSrcCount,
2311 : GDALDatasetH *pahSrcDS,
2312 : GDALTransformerArgUniquePtr hUniqueTransformArg,
2313 : GDALWarpAppOptions *psOptions, int *pbUsageError)
2314 : {
2315 1172 : CPLErrorReset();
2316 1172 : if (pszDest == nullptr && hDstDS == nullptr)
2317 : {
2318 0 : CPLError(CE_Failure, CPLE_AppDefined,
2319 : "pszDest == NULL && hDstDS == NULL");
2320 :
2321 0 : if (pbUsageError)
2322 0 : *pbUsageError = TRUE;
2323 0 : return nullptr;
2324 : }
2325 1172 : if (pszDest == nullptr)
2326 90 : pszDest = GDALGetDescription(hDstDS);
2327 :
2328 : #ifdef DEBUG
2329 1172 : GDALDataset *poDstDS = GDALDataset::FromHandle(hDstDS);
2330 : const int nExpectedRefCountAtEnd =
2331 1172 : (poDstDS != nullptr) ? poDstDS->GetRefCount() : 1;
2332 : (void)nExpectedRefCountAtEnd;
2333 : #endif
2334 1172 : const bool bDropDstDSRef = (hDstDS != nullptr);
2335 1172 : if (hDstDS != nullptr)
2336 93 : GDALReferenceDataset(hDstDS);
2337 :
2338 1172 : bool bVerticalShiftApplied = false;
2339 :
2340 1172 : if (psOptions->bNoVShift)
2341 : {
2342 1 : psOptions->aosTransformerOptions.SetNameValue("@STRIP_VERT_CS", "YES");
2343 : }
2344 1171 : else if (nSrcCount)
2345 : {
2346 1165 : bool bSrcHasVertAxis = false;
2347 1165 : bool bDstHasVertAxis = false;
2348 2330 : OGRSpatialReference oSRSSrc;
2349 2330 : OGRSpatialReference oSRSDst;
2350 :
2351 : bVerticalShiftApplied =
2352 1165 : MustApplyVerticalShift(pahSrcDS[0], psOptions, oSRSSrc, oSRSDst,
2353 : bSrcHasVertAxis, bDstHasVertAxis);
2354 1165 : if (bVerticalShiftApplied)
2355 : {
2356 : psOptions->aosTransformerOptions.SetNameValue("PROMOTE_TO_3D",
2357 20 : "YES");
2358 : }
2359 : }
2360 :
2361 1172 : bool bVRT = false;
2362 1172 : if (!CheckOptions(pszDest, hDstDS, nSrcCount, pahSrcDS, psOptions, bVRT,
2363 : pbUsageError))
2364 : {
2365 1 : return nullptr;
2366 : }
2367 :
2368 : /* -------------------------------------------------------------------- */
2369 : /* If we have a cutline datasource read it and attach it in the */
2370 : /* warp options. */
2371 : /* -------------------------------------------------------------------- */
2372 1171 : std::unique_ptr<OGRGeometry> poCutline;
2373 1171 : if (!ProcessCutlineOptions(nSrcCount, pahSrcDS, psOptions, poCutline))
2374 : {
2375 11 : return nullptr;
2376 : }
2377 :
2378 : /* -------------------------------------------------------------------- */
2379 : /* If the target dataset does not exist, we need to create it. */
2380 : /* -------------------------------------------------------------------- */
2381 : const bool bInitDestSetByUser =
2382 1160 : (psOptions->aosWarpOptions.FetchNameValue("INIT_DEST") != nullptr);
2383 :
2384 1160 : const bool bFigureoutCorrespondingWindow =
2385 2227 : (hDstDS != nullptr) ||
2386 1067 : (((psOptions->nForcePixels != 0 && psOptions->nForceLines != 0) ||
2387 899 : (psOptions->dfXRes != 0 && psOptions->dfYRes != 0)) &&
2388 261 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
2389 133 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0));
2390 :
2391 1160 : const char *pszMethod = FetchSrcMethod(psOptions->aosTransformerOptions);
2392 40 : if (pszMethod && EQUAL(pszMethod, "GCP_TPS") &&
2393 1205 : psOptions->dfErrorThreshold > 0 &&
2394 5 : !psOptions->aosTransformerOptions.FetchNameValue(
2395 : "SRC_APPROX_ERROR_IN_PIXEL"))
2396 : {
2397 : psOptions->aosTransformerOptions.SetNameValue(
2398 : "SRC_APPROX_ERROR_IN_PIXEL",
2399 5 : CPLSPrintf("%g", psOptions->dfErrorThreshold));
2400 : }
2401 :
2402 1160 : if (hDstDS == nullptr)
2403 : {
2404 1067 : hDstDS = CreateOutput(pszDest, nSrcCount, pahSrcDS, psOptions,
2405 : bInitDestSetByUser, hUniqueTransformArg);
2406 1067 : if (!hDstDS)
2407 : {
2408 17 : return nullptr;
2409 : }
2410 : #ifdef DEBUG
2411 : // Do not remove this if the #ifdef DEBUG before is still there !
2412 1050 : poDstDS = GDALDataset::FromHandle(hDstDS);
2413 1050 : CPL_IGNORE_RET_VAL(poDstDS);
2414 : #endif
2415 : }
2416 : else
2417 : {
2418 93 : if (psOptions->aosWarpOptions.FetchNameValue("SKIP_NOSOURCE") ==
2419 : nullptr)
2420 : {
2421 92 : CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2422 92 : psOptions->aosWarpOptions.SetNameValue("SKIP_NOSOURCE", "YES");
2423 : }
2424 : }
2425 :
2426 : /* -------------------------------------------------------------------- */
2427 : /* Detect if output has alpha channel. */
2428 : /* -------------------------------------------------------------------- */
2429 1143 : bool bEnableDstAlpha = psOptions->bEnableDstAlpha;
2430 1044 : if (!bEnableDstAlpha && GDALGetRasterCount(hDstDS) &&
2431 1043 : GDALGetRasterColorInterpretation(GDALGetRasterBand(
2432 2187 : hDstDS, GDALGetRasterCount(hDstDS))) == GCI_AlphaBand &&
2433 34 : !psOptions->bDisableSrcAlpha)
2434 : {
2435 33 : if (!psOptions->bQuiet)
2436 1 : printf("Using band %d of destination image as alpha.\n",
2437 : GDALGetRasterCount(hDstDS));
2438 :
2439 33 : bEnableDstAlpha = true;
2440 : }
2441 :
2442 : /* -------------------------------------------------------------------- */
2443 : /* Create global progress function. */
2444 : /* -------------------------------------------------------------------- */
2445 : struct Progress
2446 : {
2447 : GDALProgressFunc pfnExternalProgress;
2448 : void *pExternalProgressData;
2449 : int iSrc;
2450 : int nSrcCount;
2451 : GDALDatasetH *pahSrcDS;
2452 :
2453 18993 : int Do(double dfComplete)
2454 : {
2455 37986 : CPLString osMsg;
2456 : osMsg.Printf("Processing %s [%d/%d]",
2457 18993 : CPLGetFilename(GDALGetDescription(pahSrcDS[iSrc])),
2458 18993 : iSrc + 1, nSrcCount);
2459 18993 : return pfnExternalProgress((iSrc + dfComplete) / nSrcCount,
2460 37986 : osMsg.c_str(), pExternalProgressData);
2461 : }
2462 :
2463 17656 : static int CPL_STDCALL ProgressFunc(double dfComplete, const char *,
2464 : void *pThis)
2465 : {
2466 17656 : return static_cast<Progress *>(pThis)->Do(dfComplete);
2467 : }
2468 : };
2469 :
2470 : Progress oProgress;
2471 1143 : oProgress.pfnExternalProgress = psOptions->pfnProgress;
2472 1143 : oProgress.pExternalProgressData = psOptions->pProgressData;
2473 1143 : oProgress.nSrcCount = nSrcCount;
2474 1143 : oProgress.pahSrcDS = pahSrcDS;
2475 :
2476 : /* -------------------------------------------------------------------- */
2477 : /* Loop over all source files, processing each in turn. */
2478 : /* -------------------------------------------------------------------- */
2479 1143 : bool bHasGotErr = false;
2480 2121 : for (int iSrc = 0; iSrc < nSrcCount; iSrc++)
2481 : {
2482 : GDALDatasetH hSrcDS;
2483 :
2484 : /* --------------------------------------------------------------------
2485 : */
2486 : /* Open this file. */
2487 : /* --------------------------------------------------------------------
2488 : */
2489 1165 : hSrcDS = pahSrcDS[iSrc];
2490 1165 : oProgress.iSrc = iSrc;
2491 :
2492 : /* --------------------------------------------------------------------
2493 : */
2494 : /* Check that there's at least one raster band */
2495 : /* --------------------------------------------------------------------
2496 : */
2497 1165 : if (GDALGetRasterCount(hSrcDS) == 0)
2498 : {
2499 1 : CPLError(CE_Failure, CPLE_AppDefined,
2500 : "Input file %s has no raster bands.",
2501 : GDALGetDescription(hSrcDS));
2502 1 : GDALReleaseDataset(hDstDS);
2503 187 : return nullptr;
2504 : }
2505 :
2506 : /* --------------------------------------------------------------------
2507 : */
2508 : /* Do we have a source alpha band? */
2509 : /* --------------------------------------------------------------------
2510 : */
2511 1164 : bool bEnableSrcAlpha = psOptions->bEnableSrcAlpha;
2512 1164 : if (GDALGetRasterColorInterpretation(GDALGetRasterBand(
2513 73 : hSrcDS, GDALGetRasterCount(hSrcDS))) == GCI_AlphaBand &&
2514 1164 : !bEnableSrcAlpha && !psOptions->bDisableSrcAlpha)
2515 : {
2516 31 : bEnableSrcAlpha = true;
2517 31 : if (!psOptions->bQuiet)
2518 0 : printf("Using band %d of source image as alpha.\n",
2519 : GDALGetRasterCount(hSrcDS));
2520 : }
2521 :
2522 : /* --------------------------------------------------------------------
2523 : */
2524 : /* Get the metadata of the first source DS and copy it to the */
2525 : /* destination DS. Copy Band-level metadata and other info, only */
2526 : /* if source and destination band count are equal. Any values that
2527 : */
2528 : /* conflict between source datasets are set to pszMDConflictValue.
2529 : */
2530 : /* --------------------------------------------------------------------
2531 : */
2532 1164 : ProcessMetadata(iSrc, hSrcDS, hDstDS, psOptions, bEnableDstAlpha,
2533 : bVerticalShiftApplied);
2534 :
2535 : /* --------------------------------------------------------------------
2536 : */
2537 : /* Warns if the file has a color table and something more */
2538 : /* complicated than nearest neighbour resampling is asked */
2539 : /* --------------------------------------------------------------------
2540 : */
2541 :
2542 2855 : if (psOptions->eResampleAlg != GRA_NearestNeighbour &&
2543 1650 : psOptions->eResampleAlg != GRA_Mode &&
2544 486 : GDALGetRasterColorTable(GDALGetRasterBand(hSrcDS, 1)) != nullptr)
2545 : {
2546 0 : if (!psOptions->bQuiet)
2547 0 : CPLError(
2548 : CE_Warning, CPLE_AppDefined,
2549 : "Input file %s has a color table, which will likely lead "
2550 : "to "
2551 : "bad results when using a resampling method other than "
2552 : "nearest neighbour or mode. Converting the dataset prior "
2553 : "to 24/32 bit "
2554 : "is advised.",
2555 : GDALGetDescription(hSrcDS));
2556 : }
2557 :
2558 : // For RPC warping add a few extra source pixels by default
2559 : // (probably mostly needed in the RPC DEM case)
2560 2304 : if (iSrc == 0 &&
2561 1140 : (GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) != nullptr &&
2562 3 : (pszMethod == nullptr || EQUAL(pszMethod, GDAL_MDD_RPC))))
2563 : {
2564 13 : if (!psOptions->aosWarpOptions.FetchNameValue("SOURCE_EXTRA"))
2565 : {
2566 13 : CPLDebug(
2567 : "WARP",
2568 : "Set SOURCE_EXTRA=5 warping options due to RPC warping");
2569 13 : psOptions->aosWarpOptions.SetNameValue("SOURCE_EXTRA", "5");
2570 : }
2571 :
2572 13 : if (!psOptions->aosWarpOptions.FetchNameValue("SAMPLE_STEPS") &&
2573 26 : !psOptions->aosWarpOptions.FetchNameValue("SAMPLE_GRID") &&
2574 13 : psOptions->aosTransformerOptions.FetchNameValue("RPC_DEM"))
2575 : {
2576 10 : CPLDebug("WARP", "Set SAMPLE_STEPS=ALL warping options due to "
2577 : "RPC DEM warping");
2578 10 : psOptions->aosWarpOptions.SetNameValue("SAMPLE_STEPS", "ALL");
2579 : }
2580 : }
2581 : // Also do the same for GCP TPS warping, e.g. to solve use case of
2582 : // https://github.com/OSGeo/gdal/issues/12736
2583 2278 : else if (iSrc == 0 &&
2584 1127 : (GDALGetGCPCount(hSrcDS) > 0 &&
2585 6 : (pszMethod == nullptr || EQUAL(pszMethod, "TPS"))))
2586 : {
2587 21 : if (!psOptions->aosWarpOptions.FetchNameValue("SOURCE_EXTRA"))
2588 : {
2589 21 : CPLDebug(
2590 : "WARP",
2591 : "Set SOURCE_EXTRA=5 warping options due to TPS warping");
2592 : }
2593 : }
2594 :
2595 1164 : if (iSrc > 0)
2596 : psOptions->aosWarpOptions.SetNameValue("RESET_DEST_PIXELS",
2597 24 : nullptr);
2598 :
2599 : /* --------------------------------------------------------------------
2600 : */
2601 : /* Create a transformation object from the source to */
2602 : /* destination coordinate system. */
2603 : /* --------------------------------------------------------------------
2604 : */
2605 0 : GDALTransformerArgUniquePtr hTransformArg;
2606 1164 : if (hUniqueTransformArg)
2607 1028 : hTransformArg = std::move(hUniqueTransformArg);
2608 : else
2609 : {
2610 136 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
2611 136 : hSrcDS, hDstDS, psOptions->aosTransformerOptions.List()));
2612 136 : if (hTransformArg == nullptr)
2613 : {
2614 0 : GDALReleaseDataset(hDstDS);
2615 0 : return nullptr;
2616 : }
2617 : }
2618 :
2619 1164 : GDALTransformerFunc pfnTransformer = GDALGenImgProjTransform;
2620 :
2621 : // Check if transformation is inversible
2622 : {
2623 1164 : double dfX = GDALGetRasterXSize(hDstDS) / 2.0;
2624 1164 : double dfY = GDALGetRasterYSize(hDstDS) / 2.0;
2625 1164 : double dfZ = 0;
2626 1164 : int bSuccess = false;
2627 1164 : const auto nErrorCounterBefore = CPLGetErrorCounter();
2628 1164 : pfnTransformer(hTransformArg.get(), TRUE, 1, &dfX, &dfY, &dfZ,
2629 : &bSuccess);
2630 1164 : if (!bSuccess && CPLGetErrorCounter() > nErrorCounterBefore &&
2631 0 : strstr(CPLGetLastErrorMsg(), "No inverse operation"))
2632 : {
2633 0 : GDALReleaseDataset(hDstDS);
2634 0 : return nullptr;
2635 : }
2636 : }
2637 :
2638 : /* --------------------------------------------------------------------
2639 : */
2640 : /* Determine if we must work with the full-resolution source */
2641 : /* dataset, or one of its overview level. */
2642 : /* --------------------------------------------------------------------
2643 : */
2644 1164 : GDALDataset *poSrcDS = static_cast<GDALDataset *>(hSrcDS);
2645 1164 : GDALDataset *poSrcOvrDS = nullptr;
2646 1164 : int nOvCount = poSrcDS->GetRasterBand(1)->GetOverviewCount();
2647 1164 : if (psOptions->nOvLevel <= OVR_LEVEL_AUTO && nOvCount > 0)
2648 : {
2649 22 : double dfTargetRatio = 0;
2650 22 : double dfTargetRatioX = 0;
2651 22 : double dfTargetRatioY = 0;
2652 :
2653 22 : if (bFigureoutCorrespondingWindow)
2654 : {
2655 : // If the user has explicitly set the target bounds and
2656 : // resolution, or we're updating an existing file, then figure
2657 : // out which source window corresponds to the target raster.
2658 4 : constexpr int nPointsOneDim = 10;
2659 4 : constexpr int nPoints = nPointsOneDim * nPointsOneDim;
2660 8 : std::vector<double> adfX(nPoints);
2661 8 : std::vector<double> adfY(nPoints);
2662 8 : std::vector<double> adfZ(nPoints);
2663 4 : const int nDstXSize = GDALGetRasterXSize(hDstDS);
2664 4 : const int nDstYSize = GDALGetRasterYSize(hDstDS);
2665 4 : int iPoint = 0;
2666 44 : for (int iX = 0; iX < nPointsOneDim; ++iX)
2667 : {
2668 440 : for (int iY = 0; iY < nPointsOneDim; ++iY)
2669 : {
2670 400 : adfX[iPoint] = nDstXSize * static_cast<double>(iX) /
2671 : (nPointsOneDim - 1);
2672 400 : adfY[iPoint] = nDstYSize * static_cast<double>(iY) /
2673 : (nPointsOneDim - 1);
2674 400 : iPoint++;
2675 : }
2676 : }
2677 4 : std::vector<int> abSuccess(nPoints);
2678 4 : pfnTransformer(hTransformArg.get(), TRUE, nPoints, &adfX[0],
2679 4 : &adfY[0], &adfZ[0], &abSuccess[0]);
2680 :
2681 4 : double dfMinSrcX = std::numeric_limits<double>::infinity();
2682 4 : double dfMaxSrcX = -std::numeric_limits<double>::infinity();
2683 4 : double dfMinSrcY = std::numeric_limits<double>::infinity();
2684 4 : double dfMaxSrcY = -std::numeric_limits<double>::infinity();
2685 404 : for (int i = 0; i < nPoints; i++)
2686 : {
2687 400 : if (abSuccess[i])
2688 : {
2689 400 : dfMinSrcX = std::min(dfMinSrcX, adfX[i]);
2690 400 : dfMaxSrcX = std::max(dfMaxSrcX, adfX[i]);
2691 400 : dfMinSrcY = std::min(dfMinSrcY, adfY[i]);
2692 400 : dfMaxSrcY = std::max(dfMaxSrcY, adfY[i]);
2693 : }
2694 : }
2695 4 : if (dfMaxSrcX > dfMinSrcX)
2696 : {
2697 4 : dfTargetRatioX =
2698 4 : (dfMaxSrcX - dfMinSrcX) / GDALGetRasterXSize(hDstDS);
2699 : }
2700 4 : if (dfMaxSrcY > dfMinSrcY)
2701 : {
2702 4 : dfTargetRatioY =
2703 4 : (dfMaxSrcY - dfMinSrcY) / GDALGetRasterYSize(hDstDS);
2704 : }
2705 : // take the minimum of these ratios #7019
2706 4 : dfTargetRatio = std::min(dfTargetRatioX, dfTargetRatioY);
2707 : }
2708 : else
2709 : {
2710 : /* Compute what the "natural" output resolution (in pixels)
2711 : * would be for this */
2712 : /* input dataset */
2713 : double adfSuggestedGeoTransform[6];
2714 : int nPixels, nLines;
2715 18 : if (GDALSuggestedWarpOutput(
2716 : hSrcDS, pfnTransformer, hTransformArg.get(),
2717 18 : adfSuggestedGeoTransform, &nPixels, &nLines) == CE_None)
2718 : {
2719 18 : dfTargetRatio = 1.0 / adfSuggestedGeoTransform[1];
2720 : }
2721 : }
2722 :
2723 22 : if (dfTargetRatio > 1.0)
2724 : {
2725 15 : const char *pszOversampligThreshold = CPLGetConfigOption(
2726 : "GDALWARP_OVERSAMPLING_THRESHOLD", nullptr);
2727 : const double dfOversamplingThreshold =
2728 15 : pszOversampligThreshold ? CPLAtof(pszOversampligThreshold)
2729 15 : : 1.0;
2730 :
2731 15 : const int iBestOvr = GDALBandGetBestOverviewLevel(
2732 : poSrcDS->GetRasterBand(1), dfTargetRatio,
2733 : dfOversamplingThreshold);
2734 15 : const int iOvr =
2735 15 : iBestOvr + (psOptions->nOvLevel - OVR_LEVEL_AUTO);
2736 15 : if (iOvr >= 0)
2737 : {
2738 9 : CPLDebug("WARP", "Selecting overview level %d for %s", iOvr,
2739 : GDALGetDescription(hSrcDS));
2740 : poSrcOvrDS =
2741 9 : GDALCreateOverviewDataset(poSrcDS, iOvr,
2742 : /* bThisLevelOnly = */ false);
2743 : }
2744 22 : }
2745 : }
2746 1142 : else if (psOptions->nOvLevel >= 0)
2747 : {
2748 6 : poSrcOvrDS = GDALCreateOverviewDataset(poSrcDS, psOptions->nOvLevel,
2749 : /* bThisLevelOnly = */ true);
2750 6 : if (poSrcOvrDS == nullptr)
2751 : {
2752 1 : if (!psOptions->bQuiet)
2753 : {
2754 1 : CPLError(CE_Warning, CPLE_AppDefined,
2755 : "cannot get overview level %d for "
2756 : "dataset %s. Defaulting to level %d",
2757 : psOptions->nOvLevel, GDALGetDescription(hSrcDS),
2758 : nOvCount - 1);
2759 : }
2760 1 : if (nOvCount > 0)
2761 : poSrcOvrDS =
2762 1 : GDALCreateOverviewDataset(poSrcDS, nOvCount - 1,
2763 : /* bThisLevelOnly = */ false);
2764 : }
2765 : else
2766 : {
2767 5 : CPLDebug("WARP", "Selecting overview level %d for %s",
2768 : psOptions->nOvLevel, GDALGetDescription(hSrcDS));
2769 : }
2770 : }
2771 :
2772 1164 : if (poSrcOvrDS == nullptr)
2773 1149 : GDALReferenceDataset(hSrcDS);
2774 :
2775 1164 : GDALDatasetH hWrkSrcDS =
2776 1164 : poSrcOvrDS ? static_cast<GDALDatasetH>(poSrcOvrDS) : hSrcDS;
2777 :
2778 : /* --------------------------------------------------------------------
2779 : */
2780 : /* Clear temporary INIT_DEST settings after the first image. */
2781 : /* --------------------------------------------------------------------
2782 : */
2783 1164 : if (psOptions->bCreateOutput && iSrc == 1)
2784 22 : psOptions->aosWarpOptions.SetNameValue("INIT_DEST", nullptr);
2785 :
2786 : /* --------------------------------------------------------------------
2787 : */
2788 : /* Define SKIP_NOSOURCE after the first image (since
2789 : * initialization*/
2790 : /* has already be done). */
2791 : /* --------------------------------------------------------------------
2792 : */
2793 1164 : if (iSrc == 1 && psOptions->aosWarpOptions.FetchNameValue(
2794 : "SKIP_NOSOURCE") == nullptr)
2795 : {
2796 22 : CPLDebug("GDALWARP", "Defining SKIP_NOSOURCE=YES");
2797 22 : psOptions->aosWarpOptions.SetNameValue("SKIP_NOSOURCE", "YES");
2798 : }
2799 :
2800 : /* --------------------------------------------------------------------
2801 : */
2802 : /* Setup warp options. */
2803 : /* --------------------------------------------------------------------
2804 : */
2805 : std::unique_ptr<GDALWarpOptions, decltype(&GDALDestroyWarpOptions)>
2806 1164 : psWO(GDALCreateWarpOptions(), GDALDestroyWarpOptions);
2807 :
2808 1164 : psWO->papszWarpOptions = CSLDuplicate(psOptions->aosWarpOptions.List());
2809 1164 : psWO->eWorkingDataType = psOptions->eWorkingType;
2810 :
2811 1164 : psWO->eResampleAlg = psOptions->eResampleAlg;
2812 :
2813 1164 : psWO->hSrcDS = hWrkSrcDS;
2814 1164 : psWO->hDstDS = hDstDS;
2815 :
2816 1164 : if (!bVRT)
2817 : {
2818 984 : if (psOptions->pfnProgress == GDALDummyProgress)
2819 : {
2820 862 : psWO->pfnProgress = GDALDummyProgress;
2821 862 : psWO->pProgressArg = nullptr;
2822 : }
2823 : else
2824 : {
2825 122 : psWO->pfnProgress = Progress::ProgressFunc;
2826 122 : psWO->pProgressArg = &oProgress;
2827 : }
2828 : }
2829 :
2830 1164 : if (psOptions->dfWarpMemoryLimit != 0.0)
2831 35 : psWO->dfWarpMemoryLimit = psOptions->dfWarpMemoryLimit;
2832 :
2833 : /* --------------------------------------------------------------------
2834 : */
2835 : /* Setup band mapping. */
2836 : /* --------------------------------------------------------------------
2837 : */
2838 1164 : if (psOptions->anSrcBands.empty())
2839 : {
2840 1137 : if (bEnableSrcAlpha)
2841 62 : psWO->nBandCount = GDALGetRasterCount(hWrkSrcDS) - 1;
2842 : else
2843 1075 : psWO->nBandCount = GDALGetRasterCount(hWrkSrcDS);
2844 : }
2845 : else
2846 : {
2847 27 : psWO->nBandCount = static_cast<int>(psOptions->anSrcBands.size());
2848 : }
2849 :
2850 : const int nNeededDstBands =
2851 1164 : psWO->nBandCount + (bEnableDstAlpha ? 1 : 0);
2852 1164 : if (nNeededDstBands > GDALGetRasterCount(hDstDS))
2853 : {
2854 1 : CPLError(CE_Failure, CPLE_AppDefined,
2855 : "Destination dataset has %d bands, but at least %d "
2856 : "are needed",
2857 : GDALGetRasterCount(hDstDS), nNeededDstBands);
2858 1 : GDALReleaseDataset(hWrkSrcDS);
2859 1 : GDALReleaseDataset(hDstDS);
2860 1 : return nullptr;
2861 : }
2862 :
2863 2326 : psWO->panSrcBands =
2864 1163 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
2865 2326 : psWO->panDstBands =
2866 1163 : static_cast<int *>(CPLMalloc(psWO->nBandCount * sizeof(int)));
2867 1163 : if (psOptions->anSrcBands.empty())
2868 : {
2869 2644 : for (int i = 0; i < psWO->nBandCount; i++)
2870 : {
2871 1508 : psWO->panSrcBands[i] = i + 1;
2872 1508 : psWO->panDstBands[i] = i + 1;
2873 : }
2874 : }
2875 : else
2876 : {
2877 68 : for (int i = 0; i < psWO->nBandCount; i++)
2878 : {
2879 86 : if (psOptions->anSrcBands[i] <= 0 ||
2880 43 : psOptions->anSrcBands[i] > GDALGetRasterCount(hSrcDS))
2881 : {
2882 1 : CPLError(CE_Failure, CPLE_AppDefined,
2883 : "-srcband[%d] = %d is invalid", i,
2884 1 : psOptions->anSrcBands[i]);
2885 1 : GDALReleaseDataset(hWrkSrcDS);
2886 1 : GDALReleaseDataset(hDstDS);
2887 1 : return nullptr;
2888 : }
2889 84 : if (psOptions->anDstBands[i] <= 0 ||
2890 42 : psOptions->anDstBands[i] > GDALGetRasterCount(hDstDS))
2891 : {
2892 1 : CPLError(CE_Failure, CPLE_AppDefined,
2893 : "-dstband[%d] = %d is invalid", i,
2894 1 : psOptions->anDstBands[i]);
2895 1 : GDALReleaseDataset(hWrkSrcDS);
2896 1 : GDALReleaseDataset(hDstDS);
2897 1 : return nullptr;
2898 : }
2899 41 : psWO->panSrcBands[i] = psOptions->anSrcBands[i];
2900 41 : psWO->panDstBands[i] = psOptions->anDstBands[i];
2901 : }
2902 : }
2903 :
2904 : /* --------------------------------------------------------------------
2905 : */
2906 : /* Setup alpha bands used if any. */
2907 : /* --------------------------------------------------------------------
2908 : */
2909 1161 : if (bEnableSrcAlpha)
2910 70 : psWO->nSrcAlphaBand = GDALGetRasterCount(hWrkSrcDS);
2911 :
2912 1161 : if (bEnableDstAlpha)
2913 : {
2914 137 : if (psOptions->anSrcBands.empty())
2915 128 : psWO->nDstAlphaBand = GDALGetRasterCount(hDstDS);
2916 : else
2917 9 : psWO->nDstAlphaBand =
2918 9 : static_cast<int>(psOptions->anDstBands.size()) + 1;
2919 : }
2920 :
2921 : /* ------------------------------------------------------------------ */
2922 : /* Setup NODATA options. */
2923 : /* ------------------------------------------------------------------ */
2924 1161 : if (SetupNoData(pszDest, iSrc, hSrcDS, hWrkSrcDS, hDstDS, psWO.get(),
2925 : psOptions, bEnableDstAlpha,
2926 1161 : bInitDestSetByUser) != CE_None)
2927 : {
2928 2 : GDALReleaseDataset(hWrkSrcDS);
2929 2 : GDALReleaseDataset(hDstDS);
2930 2 : return nullptr;
2931 : }
2932 :
2933 1159 : oProgress.Do(0);
2934 :
2935 : /* --------------------------------------------------------------------
2936 : */
2937 : /* For the first source image of a newly created dataset, decide */
2938 : /* if we can safely enable SKIP_NOSOURCE optimization. */
2939 : /* --------------------------------------------------------------------
2940 : */
2941 1159 : SetupSkipNoSource(iSrc, hDstDS, psWO.get(), psOptions);
2942 :
2943 : /* --------------------------------------------------------------------
2944 : */
2945 : /* In some cases, RPC evaluation can find valid input pixel for */
2946 : /* output pixels that are outside the footprint of the source */
2947 : /* dataset, so limit the area we update in the target dataset from
2948 : */
2949 : /* the suggested warp output (only in cases where
2950 : * SKIP_NOSOURCE=YES) */
2951 : /* --------------------------------------------------------------------
2952 : */
2953 1159 : int nWarpDstXOff = 0;
2954 1159 : int nWarpDstYOff = 0;
2955 1159 : int nWarpDstXSize = GDALGetRasterXSize(hDstDS);
2956 1159 : int nWarpDstYSize = GDALGetRasterYSize(hDstDS);
2957 :
2958 1159 : if (!AdjustOutputExtentForRPC(hSrcDS, hDstDS, pfnTransformer,
2959 : hTransformArg.get(), psWO.get(),
2960 : psOptions, nWarpDstXOff, nWarpDstYOff,
2961 : nWarpDstXSize, nWarpDstYSize))
2962 : {
2963 1 : GDALReleaseDataset(hWrkSrcDS);
2964 1 : continue;
2965 : }
2966 :
2967 : /* We need to recreate the transform when operating on an overview */
2968 1158 : if (poSrcOvrDS != nullptr)
2969 : {
2970 15 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
2971 15 : hWrkSrcDS, hDstDS, psOptions->aosTransformerOptions.List()));
2972 : }
2973 :
2974 1158 : bool bUseApproxTransformer = psOptions->dfErrorThreshold != 0.0;
2975 :
2976 1158 : if (!psOptions->bNoVShift)
2977 : {
2978 : // Can modify psWO->papszWarpOptions
2979 1157 : if (ApplyVerticalShift(hWrkSrcDS, psOptions, psWO.get()))
2980 : {
2981 20 : bUseApproxTransformer = false;
2982 : }
2983 : }
2984 :
2985 : /* --------------------------------------------------------------------
2986 : */
2987 : /* Warp the transformer with a linear approximator unless the */
2988 : /* acceptable error is zero. */
2989 : /* --------------------------------------------------------------------
2990 : */
2991 1158 : if (bUseApproxTransformer)
2992 : {
2993 1124 : hTransformArg.reset(GDALCreateApproxTransformer(
2994 : GDALGenImgProjTransform, hTransformArg.release(),
2995 : psOptions->dfErrorThreshold));
2996 1124 : pfnTransformer = GDALApproxTransform;
2997 1124 : GDALApproxTransformerOwnsSubtransformer(hTransformArg.get(), TRUE);
2998 : }
2999 :
3000 : /* --------------------------------------------------------------------
3001 : */
3002 : /* If we have a cutline, transform it into the source */
3003 : /* pixel/line coordinate system and insert into warp options. */
3004 : /* --------------------------------------------------------------------
3005 : */
3006 1158 : if (poCutline)
3007 : {
3008 : CPLErr eError;
3009 46 : eError = TransformCutlineToSource(
3010 : GDALDataset::FromHandle(hWrkSrcDS), poCutline.get(),
3011 46 : &(psWO->papszWarpOptions),
3012 46 : psOptions->aosTransformerOptions.List());
3013 46 : if (eError == CE_Failure)
3014 : {
3015 1 : GDALReleaseDataset(hWrkSrcDS);
3016 1 : GDALReleaseDataset(hDstDS);
3017 1 : return nullptr;
3018 : }
3019 : }
3020 :
3021 : /* --------------------------------------------------------------------
3022 : */
3023 : /* If we are producing VRT output, then just initialize it with */
3024 : /* the warp options and write out now rather than proceeding */
3025 : /* with the operations. */
3026 : /* --------------------------------------------------------------------
3027 : */
3028 1157 : if (bVRT)
3029 : {
3030 180 : GDALSetMetadataItem(hDstDS, "SrcOvrLevel",
3031 : CPLSPrintf("%d", psOptions->nOvLevel), nullptr);
3032 :
3033 : // In case of success, hDstDS has become the owner of hTransformArg
3034 : // so we need to release it
3035 180 : psWO->pfnTransformer = pfnTransformer;
3036 180 : psWO->pTransformerArg = hTransformArg.release();
3037 180 : CPLErr eErr = GDALInitializeWarpedVRT(hDstDS, psWO.get());
3038 180 : if (eErr != CE_None)
3039 : {
3040 : // In case of error, reacquire psWO->pTransformerArg
3041 1 : hTransformArg.reset(psWO->pTransformerArg);
3042 : }
3043 180 : GDALReleaseDataset(hWrkSrcDS);
3044 180 : if (eErr != CE_None)
3045 : {
3046 1 : GDALReleaseDataset(hDstDS);
3047 1 : return nullptr;
3048 : }
3049 :
3050 179 : if (!EQUAL(pszDest, ""))
3051 : {
3052 : const bool bWasFailureBefore =
3053 20 : (CPLGetLastErrorType() == CE_Failure);
3054 20 : GDALFlushCache(hDstDS);
3055 20 : if (!bWasFailureBefore && CPLGetLastErrorType() == CE_Failure)
3056 : {
3057 1 : GDALReleaseDataset(hDstDS);
3058 1 : hDstDS = nullptr;
3059 : }
3060 : }
3061 :
3062 179 : if (hDstDS)
3063 178 : oProgress.Do(1);
3064 :
3065 179 : return hDstDS;
3066 : }
3067 :
3068 : /* --------------------------------------------------------------------
3069 : */
3070 : /* Initialize and execute the warp. */
3071 : /* --------------------------------------------------------------------
3072 : */
3073 1954 : GDALWarpOperation oWO;
3074 :
3075 1954 : if (oWO.Initialize(psWO.get(), pfnTransformer,
3076 1954 : std::move(hTransformArg)) == CE_None)
3077 : {
3078 : CPLErr eErr;
3079 973 : if (psOptions->bMulti)
3080 6 : eErr = oWO.ChunkAndWarpMulti(nWarpDstXOff, nWarpDstYOff,
3081 : nWarpDstXSize, nWarpDstYSize);
3082 : else
3083 967 : eErr = oWO.ChunkAndWarpImage(nWarpDstXOff, nWarpDstYOff,
3084 : nWarpDstXSize, nWarpDstYSize);
3085 973 : if (eErr != CE_None)
3086 5 : bHasGotErr = true;
3087 : }
3088 : else
3089 : {
3090 4 : bHasGotErr = true;
3091 : }
3092 :
3093 : /* --------------------------------------------------------------------
3094 : */
3095 : /* Cleanup */
3096 : /* --------------------------------------------------------------------
3097 : */
3098 977 : GDALReleaseDataset(hWrkSrcDS);
3099 : }
3100 :
3101 : /* -------------------------------------------------------------------- */
3102 : /* Final Cleanup. */
3103 : /* -------------------------------------------------------------------- */
3104 956 : const bool bWasFailureBefore = (CPLGetLastErrorType() == CE_Failure);
3105 956 : GDALFlushCache(hDstDS);
3106 956 : if (!bWasFailureBefore && CPLGetLastErrorType() == CE_Failure)
3107 : {
3108 1 : bHasGotErr = true;
3109 : }
3110 :
3111 956 : if (bHasGotErr || bDropDstDSRef)
3112 101 : GDALReleaseDataset(hDstDS);
3113 :
3114 : #ifdef DEBUG
3115 956 : if (!bHasGotErr || bDropDstDSRef)
3116 : {
3117 946 : CPLAssert(poDstDS->GetRefCount() == nExpectedRefCountAtEnd);
3118 : }
3119 : #endif
3120 :
3121 956 : return bHasGotErr ? nullptr : hDstDS;
3122 : }
3123 :
3124 : /************************************************************************/
3125 : /* ValidateCutline() */
3126 : /* Same as OGR_G_IsValid() except that it processes polygon per polygon*/
3127 : /* without paying attention to MultiPolygon specific validity rules. */
3128 : /************************************************************************/
3129 :
3130 241 : static bool ValidateCutline(const OGRGeometry *poGeom, bool bVerbose)
3131 : {
3132 241 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3133 241 : if (eType == wkbMultiPolygon)
3134 : {
3135 164 : for (const auto *poSubGeom : *(poGeom->toMultiPolygon()))
3136 : {
3137 88 : if (!ValidateCutline(poSubGeom, bVerbose))
3138 6 : return false;
3139 : }
3140 : }
3141 159 : else if (eType == wkbPolygon)
3142 : {
3143 158 : std::string osReason;
3144 158 : if (OGRGeometryFactory::haveGEOS() && !poGeom->IsValid(&osReason))
3145 : {
3146 6 : if (!bVerbose)
3147 6 : return false;
3148 :
3149 2 : char *pszWKT = nullptr;
3150 2 : poGeom->exportToWkt(&pszWKT);
3151 2 : CPLDebug("GDALWARP", "WKT = \"%s\"", pszWKT ? pszWKT : "(null)");
3152 : const char *pszFile =
3153 2 : CPLGetConfigOption("GDALWARP_DUMP_WKT_TO_FILE", nullptr);
3154 2 : if (pszFile && pszWKT)
3155 : {
3156 : FILE *f =
3157 0 : EQUAL(pszFile, "stderr") ? stderr : fopen(pszFile, "wb");
3158 0 : if (f)
3159 : {
3160 0 : fprintf(f, "id,WKT\n");
3161 0 : fprintf(f, "1,\"%s\"\n", pszWKT);
3162 0 : if (!EQUAL(pszFile, "stderr"))
3163 0 : fclose(f);
3164 : }
3165 : }
3166 2 : CPLFree(pszWKT);
3167 :
3168 2 : if (CPLTestBool(
3169 : CPLGetConfigOption("GDALWARP_IGNORE_BAD_CUTLINE", "NO")))
3170 : {
3171 0 : CPLError(CE_Warning, CPLE_AppDefined,
3172 : "Cutline polygon is invalid: %s.", osReason.c_str());
3173 : }
3174 : else
3175 : {
3176 2 : CPLError(CE_Failure, CPLE_AppDefined,
3177 : "Cutline polygon is invalid: %s.", osReason.c_str());
3178 2 : return false;
3179 : }
3180 : }
3181 : }
3182 : else
3183 : {
3184 1 : if (bVerbose)
3185 : {
3186 1 : CPLError(CE_Failure, CPLE_AppDefined,
3187 : "Cutline not of polygon type.");
3188 : }
3189 1 : return false;
3190 : }
3191 :
3192 228 : return true;
3193 : }
3194 :
3195 : /************************************************************************/
3196 : /* LoadCutline() */
3197 : /* */
3198 : /* Load blend cutline from OGR datasource. */
3199 : /************************************************************************/
3200 :
3201 58 : static CPLErr LoadCutline(const std::string &osCutlineDSNameOrWKT,
3202 : const std::string &osSRS, const std::string &osCLayer,
3203 : const std::string &osCWHERE,
3204 : const std::string &osCSQL, OGRGeometryH *phCutlineRet)
3205 :
3206 : {
3207 58 : if (STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "POLYGON(") ||
3208 58 : STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "POLYGON (") ||
3209 160 : STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "MULTIPOLYGON(") ||
3210 44 : STARTS_WITH_CI(osCutlineDSNameOrWKT.c_str(), "MULTIPOLYGON ("))
3211 : {
3212 28 : OGRSpatialReferenceRefCountedPtr poSRS;
3213 14 : if (!osSRS.empty())
3214 : {
3215 8 : poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
3216 8 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3217 8 : poSRS->SetFromUserInput(osSRS.c_str());
3218 : }
3219 :
3220 14 : auto [poGeom, _] = OGRGeometryFactory::createFromWkt(
3221 14 : osCutlineDSNameOrWKT.c_str(), poSRS.get());
3222 14 : *phCutlineRet = OGRGeometry::ToHandle(poGeom.release());
3223 14 : return *phCutlineRet ? CE_None : CE_Failure;
3224 : }
3225 :
3226 : /* -------------------------------------------------------------------- */
3227 : /* Open source vector dataset. */
3228 : /* -------------------------------------------------------------------- */
3229 : auto poDS = std::unique_ptr<GDALDataset>(
3230 88 : GDALDataset::Open(osCutlineDSNameOrWKT.c_str(), GDAL_OF_VECTOR));
3231 44 : if (poDS == nullptr)
3232 : {
3233 1 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s.",
3234 : osCutlineDSNameOrWKT.c_str());
3235 1 : return CE_Failure;
3236 : }
3237 :
3238 : /* -------------------------------------------------------------------- */
3239 : /* Get the source layer */
3240 : /* -------------------------------------------------------------------- */
3241 43 : OGRLayer *poLayer = nullptr;
3242 :
3243 43 : if (!osCSQL.empty())
3244 2 : poLayer = poDS->ExecuteSQL(osCSQL.c_str(), nullptr, nullptr);
3245 41 : else if (!osCLayer.empty())
3246 15 : poLayer = poDS->GetLayerByName(osCLayer.c_str());
3247 : else
3248 26 : poLayer = poDS->GetLayer(0);
3249 :
3250 43 : if (poLayer == nullptr)
3251 : {
3252 1 : CPLError(CE_Failure, CPLE_AppDefined,
3253 : "Failed to identify source layer from datasource.");
3254 1 : return CE_Failure;
3255 : }
3256 :
3257 : /* -------------------------------------------------------------------- */
3258 : /* Apply WHERE clause if there is one. */
3259 : /* -------------------------------------------------------------------- */
3260 42 : if (!osCWHERE.empty())
3261 1 : poLayer->SetAttributeFilter(osCWHERE.c_str());
3262 :
3263 : /* -------------------------------------------------------------------- */
3264 : /* Collect the geometries from this layer, and build list of */
3265 : /* burn values. */
3266 : /* -------------------------------------------------------------------- */
3267 84 : auto poMultiPolygon = std::make_unique<OGRMultiPolygon>();
3268 :
3269 81 : for (auto &&poFeature : poLayer)
3270 : {
3271 42 : auto poGeom = std::unique_ptr<OGRGeometry>(poFeature->StealGeometry());
3272 42 : if (poGeom == nullptr)
3273 : {
3274 1 : CPLError(CE_Failure, CPLE_AppDefined,
3275 : "Cutline feature without a geometry.");
3276 1 : goto error;
3277 : }
3278 :
3279 41 : if (!ValidateCutline(poGeom.get(), true))
3280 : {
3281 2 : goto error;
3282 : }
3283 :
3284 39 : OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
3285 :
3286 39 : if (eType == wkbPolygon)
3287 30 : poMultiPolygon->addGeometry(std::move(poGeom));
3288 9 : else if (eType == wkbMultiPolygon)
3289 : {
3290 19 : for (const auto *poSubGeom : poGeom->toMultiPolygon())
3291 : {
3292 10 : poMultiPolygon->addGeometry(poSubGeom);
3293 : }
3294 : }
3295 : }
3296 :
3297 39 : if (poMultiPolygon->IsEmpty())
3298 : {
3299 1 : CPLError(CE_Failure, CPLE_AppDefined,
3300 : "Did not get any cutline features.");
3301 1 : goto error;
3302 : }
3303 :
3304 : /* -------------------------------------------------------------------- */
3305 : /* Ensure the coordinate system gets set on the geometry. */
3306 : /* -------------------------------------------------------------------- */
3307 38 : if (!osSRS.empty())
3308 : {
3309 2 : auto poSRS = OGRSpatialReferenceRefCountedPtr::makeInstance();
3310 1 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3311 1 : poSRS->SetFromUserInput(osSRS.c_str());
3312 1 : poMultiPolygon->assignSpatialReference(poSRS.get());
3313 : }
3314 : else
3315 : {
3316 37 : poMultiPolygon->assignSpatialReference(poLayer->GetSpatialRef());
3317 : }
3318 :
3319 38 : *phCutlineRet = OGRGeometry::ToHandle(poMultiPolygon.release());
3320 :
3321 : /* -------------------------------------------------------------------- */
3322 : /* Cleanup */
3323 : /* -------------------------------------------------------------------- */
3324 38 : if (!osCSQL.empty())
3325 1 : poDS->ReleaseResultSet(poLayer);
3326 :
3327 38 : return CE_None;
3328 :
3329 4 : error:
3330 4 : if (!osCSQL.empty())
3331 1 : poDS->ReleaseResultSet(poLayer);
3332 :
3333 4 : return CE_Failure;
3334 : }
3335 :
3336 : /************************************************************************/
3337 : /* GDALWarpCreateOutput() */
3338 : /* */
3339 : /* Create the output file based on various command line options, */
3340 : /* and the input file. */
3341 : /* If there's just one source file, then hUniqueTransformArg will */
3342 : /* be set in order them to be reused by main function. This saves */
3343 : /* transform recomputation, which can be expensive in the -tps case*/
3344 : /************************************************************************/
3345 :
3346 1071 : static GDALDatasetH GDALWarpCreateOutput(
3347 : int nSrcCount, GDALDatasetH *pahSrcDS, const char *pszFilename,
3348 : const char *pszFormat, char **papszTO, CSLConstList papszCreateOptions,
3349 : GDALDataType eDT, GDALTransformerArgUniquePtr &hUniqueTransformArg,
3350 : bool bSetColorInterpretation, GDALWarpAppOptions *psOptions,
3351 : bool bUpdateTransformerWithDestGT)
3352 :
3353 : {
3354 : GDALDriverH hDriver;
3355 : GDALDatasetH hDstDS;
3356 1071 : GDALRasterAttributeTableH hRAT = nullptr;
3357 1071 : double dfWrkMinX = 0, dfWrkMaxX = 0, dfWrkMinY = 0, dfWrkMaxY = 0;
3358 1071 : double dfWrkResX = 0, dfWrkResY = 0;
3359 1071 : int nDstBandCount = 0;
3360 2142 : std::vector<GDALColorInterp> apeColorInterpretations;
3361 1071 : bool bVRT = false;
3362 :
3363 1071 : if (EQUAL(pszFormat, "VRT"))
3364 182 : bVRT = true;
3365 :
3366 : // Special case for geographic to Mercator (typically EPSG:4326 to EPSG:3857)
3367 : // where latitudes close to 90 go to infinity
3368 : // We clamp latitudes between ~ -85 and ~ 85 degrees.
3369 1071 : const char *pszDstSRS = CSLFetchNameValue(papszTO, "DST_SRS");
3370 1071 : if (nSrcCount == 1 && pszDstSRS && psOptions->dfMinX == 0.0 &&
3371 174 : psOptions->dfMinY == 0.0 && psOptions->dfMaxX == 0.0 &&
3372 174 : psOptions->dfMaxY == 0.0)
3373 : {
3374 174 : auto hSrcDS = pahSrcDS[0];
3375 348 : const auto osSrcSRS = GetSrcDSProjection(pahSrcDS[0], papszTO);
3376 348 : OGRSpatialReference oSrcSRS;
3377 174 : oSrcSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3378 174 : oSrcSRS.SetFromUserInput(osSrcSRS.c_str());
3379 348 : OGRSpatialReference oDstSRS;
3380 174 : oDstSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
3381 174 : oDstSRS.SetFromUserInput(pszDstSRS);
3382 174 : const char *pszProjection = oDstSRS.GetAttrValue("PROJECTION");
3383 174 : const char *pszMethod = FetchSrcMethod(papszTO);
3384 174 : double adfSrcGT[6] = {0};
3385 : // This MAX_LAT values is equivalent to the semi_major_axis * PI
3386 : // easting/northing value only for EPSG:3857, but it is also quite
3387 : // reasonable for other Mercator projections
3388 174 : constexpr double MAX_LAT = 85.0511287798066;
3389 174 : constexpr double EPS = 1e-3;
3390 5 : const auto GetMinLon = [&adfSrcGT]() { return adfSrcGT[0]; };
3391 5 : const auto GetMaxLon = [&adfSrcGT, hSrcDS]()
3392 5 : { return adfSrcGT[0] + adfSrcGT[1] * GDALGetRasterXSize(hSrcDS); };
3393 5 : const auto GetMinLat = [&adfSrcGT, hSrcDS]()
3394 5 : { return adfSrcGT[3] + adfSrcGT[5] * GDALGetRasterYSize(hSrcDS); };
3395 6 : const auto GetMaxLat = [&adfSrcGT]() { return adfSrcGT[3]; };
3396 240 : if (oSrcSRS.IsGeographic() && !oSrcSRS.IsDerivedGeographic() &&
3397 39 : pszProjection && EQUAL(pszProjection, SRS_PT_MERCATOR_1SP) &&
3398 3 : oDstSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0) == 0 &&
3399 0 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
3400 3 : CSLFetchNameValue(papszTO, "COORDINATE_OPERATION") == nullptr &&
3401 3 : CSLFetchNameValue(papszTO, "SRC_METHOD") == nullptr &&
3402 3 : CSLFetchNameValue(papszTO, "DST_METHOD") == nullptr &&
3403 3 : GDALGetGeoTransform(hSrcDS, adfSrcGT) == CE_None &&
3404 6 : adfSrcGT[2] == 0 && adfSrcGT[4] == 0 && adfSrcGT[5] < 0 &&
3405 9 : GetMinLon() >= -180 - EPS && GetMaxLon() <= 180 + EPS &&
3406 6 : ((GetMaxLat() > MAX_LAT && GetMinLat() < MAX_LAT) ||
3407 2 : (GetMaxLat() > -MAX_LAT && GetMinLat() < -MAX_LAT)) &&
3408 242 : GDALGetMetadata(hSrcDS, "GEOLOC_ARRAY") == nullptr &&
3409 2 : GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) == nullptr)
3410 : {
3411 : auto poCT = std::unique_ptr<OGRCoordinateTransformation>(
3412 4 : OGRCreateCoordinateTransformation(&oSrcSRS, &oDstSRS));
3413 2 : if (poCT)
3414 : {
3415 2 : double xLL = std::max(GetMinLon(), -180.0);
3416 2 : double yLL = std::max(GetMinLat(), -MAX_LAT);
3417 2 : double xUR = std::min(GetMaxLon(), 180.0);
3418 2 : double yUR = std::min(GetMaxLat(), MAX_LAT);
3419 4 : if (poCT->Transform(1, &xLL, &yLL) &&
3420 2 : poCT->Transform(1, &xUR, &yUR))
3421 : {
3422 2 : psOptions->dfMinX = xLL;
3423 2 : psOptions->dfMinY = yLL;
3424 2 : psOptions->dfMaxX = xUR;
3425 2 : psOptions->dfMaxY = yUR;
3426 2 : CPLError(CE_Warning, CPLE_AppDefined,
3427 : "Clamping output bounds to (%f,%f) -> (%f, %f)",
3428 : psOptions->dfMinX, psOptions->dfMinY,
3429 : psOptions->dfMaxX, psOptions->dfMaxY);
3430 : }
3431 : }
3432 : }
3433 : }
3434 :
3435 : /* If (-ts and -te) or (-tr and -te) are specified, we don't need to compute
3436 : * the suggested output extent */
3437 1071 : const bool bNeedsSuggestedWarpOutput =
3438 2142 : !(((psOptions->nForcePixels != 0 && psOptions->nForceLines != 0) ||
3439 903 : (psOptions->dfXRes != 0 && psOptions->dfYRes != 0)) &&
3440 261 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
3441 133 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0));
3442 :
3443 : // If -te is specified, not not -tr and -ts
3444 1071 : const bool bKnownTargetExtentButNotResolution =
3445 871 : !(psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
3446 866 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0) &&
3447 207 : psOptions->nForcePixels == 0 && psOptions->nForceLines == 0 &&
3448 2142 : psOptions->dfXRes == 0 && psOptions->dfYRes == 0;
3449 :
3450 : /* -------------------------------------------------------------------- */
3451 : /* Find the output driver. */
3452 : /* -------------------------------------------------------------------- */
3453 1071 : hDriver = GDALGetDriverByName(pszFormat);
3454 2142 : if (hDriver == nullptr ||
3455 1071 : (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) == nullptr &&
3456 0 : GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) ==
3457 : nullptr))
3458 : {
3459 : auto poMissingDriver =
3460 0 : GetGDALDriverManager()->GetHiddenDriverByName(pszFormat);
3461 0 : if (poMissingDriver)
3462 : {
3463 : const std::string msg =
3464 0 : GDALGetMessageAboutMissingPluginDriver(poMissingDriver);
3465 0 : printf("Output driver `%s' not found but is known. However plugin "
3466 : "%s\n",
3467 : pszFormat, msg.c_str());
3468 0 : return nullptr;
3469 : }
3470 :
3471 0 : printf("Output driver `%s' not recognised or does not support\n",
3472 : pszFormat);
3473 0 : printf("direct output file creation or CreateCopy. "
3474 : "The following format drivers are eligible for warp output:\n");
3475 :
3476 0 : for (int iDr = 0; iDr < GDALGetDriverCount(); iDr++)
3477 : {
3478 0 : hDriver = GDALGetDriver(iDr);
3479 :
3480 0 : if (GDALGetMetadataItem(hDriver, GDAL_DCAP_RASTER, nullptr) !=
3481 0 : nullptr &&
3482 0 : (GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATE, nullptr) !=
3483 0 : nullptr ||
3484 0 : GDALGetMetadataItem(hDriver, GDAL_DCAP_CREATECOPY, nullptr) !=
3485 : nullptr))
3486 : {
3487 0 : printf(" %s: %s\n", GDALGetDriverShortName(hDriver),
3488 : GDALGetDriverLongName(hDriver));
3489 : }
3490 : }
3491 0 : printf("\n");
3492 0 : return nullptr;
3493 : }
3494 :
3495 : /* -------------------------------------------------------------------- */
3496 : /* For virtual output files, we have to set a special subclass */
3497 : /* of dataset to create. */
3498 : /* -------------------------------------------------------------------- */
3499 2142 : CPLStringList aosCreateOptions(CSLDuplicate(papszCreateOptions));
3500 1071 : if (bVRT)
3501 182 : aosCreateOptions.SetNameValue("SUBCLASS", "VRTWarpedDataset");
3502 :
3503 : /* -------------------------------------------------------------------- */
3504 : /* Loop over all input files to collect extents. */
3505 : /* -------------------------------------------------------------------- */
3506 2142 : CPLString osThisTargetSRS;
3507 : {
3508 1071 : const char *pszThisTargetSRS = CSLFetchNameValue(papszTO, "DST_SRS");
3509 1071 : if (pszThisTargetSRS != nullptr)
3510 288 : osThisTargetSRS = pszThisTargetSRS;
3511 : }
3512 :
3513 2142 : CPLStringList aoTOList(papszTO, FALSE);
3514 :
3515 1071 : double dfResFromSourceAndTargetExtent =
3516 : std::numeric_limits<double>::infinity();
3517 :
3518 : /* -------------------------------------------------------------------- */
3519 : /* Establish list of files of output dataset if it already exists. */
3520 : /* -------------------------------------------------------------------- */
3521 2142 : std::set<std::string> oSetExistingDestFiles;
3522 : {
3523 1071 : CPLPushErrorHandler(CPLQuietErrorHandler);
3524 1071 : const char *const apszAllowedDrivers[] = {pszFormat, nullptr};
3525 : auto poExistingOutputDS = std::unique_ptr<GDALDataset>(
3526 2142 : GDALDataset::Open(pszFilename, GDAL_OF_RASTER, apszAllowedDrivers));
3527 1071 : if (poExistingOutputDS)
3528 : {
3529 74 : for (const char *pszFilenameInList :
3530 70 : CPLStringList(poExistingOutputDS->GetFileList()))
3531 : {
3532 : oSetExistingDestFiles.insert(
3533 37 : CPLString(pszFilenameInList).replaceAll('\\', '/'));
3534 : }
3535 : }
3536 1071 : CPLPopErrorHandler();
3537 : }
3538 2142 : std::set<std::string> oSetExistingDestFilesFoundInSource;
3539 1071 : std::unique_ptr<GDALColorTable> poCT;
3540 :
3541 2158 : for (int iSrc = 0; iSrc < nSrcCount; iSrc++)
3542 : {
3543 : /* --------------------------------------------------------------------
3544 : */
3545 : /* Check that there's at least one raster band */
3546 : /* --------------------------------------------------------------------
3547 : */
3548 1095 : GDALDatasetH hSrcDS = pahSrcDS[iSrc];
3549 1095 : if (GDALGetRasterCount(hSrcDS) == 0)
3550 : {
3551 1 : CPLError(CE_Failure, CPLE_AppDefined,
3552 : "Input file %s has no raster bands.",
3553 : GDALGetDescription(hSrcDS));
3554 8 : return nullptr;
3555 : }
3556 :
3557 : // Examine desired overview level and retrieve the corresponding dataset
3558 : // if it exists.
3559 0 : std::unique_ptr<GDALDataset> oDstDSOverview;
3560 1094 : if (psOptions->nOvLevel >= 0)
3561 : {
3562 5 : oDstDSOverview.reset(GDALCreateOverviewDataset(
3563 : GDALDataset::FromHandle(hSrcDS), psOptions->nOvLevel,
3564 : /* bThisLevelOnly = */ true));
3565 5 : if (oDstDSOverview)
3566 4 : hSrcDS = oDstDSOverview.get();
3567 : }
3568 :
3569 : /* --------------------------------------------------------------------
3570 : */
3571 : /* Check if the source dataset shares some files with the dest
3572 : * one.*/
3573 : /* --------------------------------------------------------------------
3574 : */
3575 1094 : if (!oSetExistingDestFiles.empty())
3576 : {
3577 : // We need to reopen in a temporary dataset for the particular
3578 : // case of overwritten a .tif.ovr file from a .tif
3579 : // If we probe the file list of the .tif, it will then open the
3580 : // .tif.ovr !
3581 36 : auto poSrcDS = GDALDataset::FromHandle(hSrcDS);
3582 36 : const char *const apszAllowedDrivers[] = {
3583 36 : poSrcDS->GetDriver() ? poSrcDS->GetDriver()->GetDescription()
3584 : : nullptr,
3585 36 : nullptr};
3586 : auto poSrcDSTmp = std::unique_ptr<GDALDataset>(GDALDataset::Open(
3587 72 : poSrcDS->GetDescription(), GDAL_OF_RASTER, apszAllowedDrivers));
3588 36 : if (poSrcDSTmp)
3589 : {
3590 38 : for (const char *pszFilenameInList :
3591 74 : CPLStringList(poSrcDSTmp->GetFileList()))
3592 : {
3593 : std::string osFilename =
3594 76 : CPLString(pszFilenameInList).replaceAll('\\', '/');
3595 38 : if (oSetExistingDestFiles.find(osFilename) !=
3596 76 : oSetExistingDestFiles.end())
3597 : {
3598 : oSetExistingDestFilesFoundInSource.insert(
3599 5 : std::move(osFilename));
3600 : }
3601 : }
3602 : }
3603 : }
3604 :
3605 1094 : if (eDT == GDT_Unknown)
3606 1033 : eDT = GDALGetRasterDataType(GDALGetRasterBand(hSrcDS, 1));
3607 :
3608 : /* --------------------------------------------------------------------
3609 : */
3610 : /* If we are processing the first file, and it has a raster */
3611 : /* attribute table, then we will copy it to the destination file.
3612 : */
3613 : /* --------------------------------------------------------------------
3614 : */
3615 1094 : if (iSrc == 0)
3616 : {
3617 1068 : hRAT = GDALGetDefaultRAT(GDALGetRasterBand(hSrcDS, 1));
3618 1068 : if (hRAT != nullptr)
3619 : {
3620 0 : if (psOptions->eResampleAlg != GRA_NearestNeighbour &&
3621 0 : psOptions->eResampleAlg != GRA_Mode &&
3622 0 : GDALRATGetTableType(hRAT) == GRTT_THEMATIC)
3623 : {
3624 0 : if (!psOptions->bQuiet)
3625 : {
3626 0 : CPLError(CE_Warning, CPLE_AppDefined,
3627 : "Warning: Input file %s has a thematic RAT, "
3628 : "which will likely lead "
3629 : "to bad results when using a resampling "
3630 : "method other than nearest neighbour "
3631 : "or mode so we are discarding it.\n",
3632 : GDALGetDescription(hSrcDS));
3633 : }
3634 0 : hRAT = nullptr;
3635 : }
3636 : else
3637 : {
3638 0 : if (!psOptions->bQuiet)
3639 0 : printf("Copying raster attribute table from %s to new "
3640 : "file.\n",
3641 : GDALGetDescription(hSrcDS));
3642 : }
3643 : }
3644 : }
3645 :
3646 : /* --------------------------------------------------------------------
3647 : */
3648 : /* If we are processing the first file, and it has a color */
3649 : /* table, then we will copy it to the destination file. */
3650 : /* --------------------------------------------------------------------
3651 : */
3652 1094 : if (iSrc == 0)
3653 : {
3654 1068 : auto hCT = GDALGetRasterColorTable(GDALGetRasterBand(hSrcDS, 1));
3655 1068 : if (hCT != nullptr)
3656 : {
3657 9 : poCT.reset(
3658 : GDALColorTable::FromHandle(GDALCloneColorTable(hCT)));
3659 9 : if (!psOptions->bQuiet)
3660 0 : printf("Copying color table from %s to new file.\n",
3661 : GDALGetDescription(hSrcDS));
3662 : }
3663 :
3664 1068 : if (psOptions->anDstBands.empty())
3665 : {
3666 1042 : nDstBandCount = GDALGetRasterCount(hSrcDS);
3667 2480 : for (int iBand = 0; iBand < nDstBandCount; iBand++)
3668 : {
3669 1438 : GDALColorInterp eInterp = GDALGetRasterColorInterpretation(
3670 1438 : GDALGetRasterBand(hSrcDS, iBand + 1));
3671 1438 : apeColorInterpretations.push_back(eInterp);
3672 : }
3673 :
3674 : // Do we want to generate an alpha band in the output file?
3675 1042 : if (psOptions->bEnableSrcAlpha)
3676 31 : nDstBandCount--;
3677 :
3678 1042 : if (psOptions->bEnableDstAlpha)
3679 90 : nDstBandCount++;
3680 : }
3681 : else
3682 : {
3683 68 : for (int nSrcBand : psOptions->anSrcBands)
3684 : {
3685 42 : auto hBand = GDALGetRasterBand(hSrcDS, nSrcBand);
3686 : GDALColorInterp eInterp =
3687 42 : hBand ? GDALGetRasterColorInterpretation(hBand)
3688 42 : : GCI_Undefined;
3689 42 : apeColorInterpretations.push_back(eInterp);
3690 : }
3691 26 : nDstBandCount = static_cast<int>(psOptions->anDstBands.size());
3692 26 : if (psOptions->bEnableDstAlpha)
3693 : {
3694 9 : nDstBandCount++;
3695 9 : apeColorInterpretations.push_back(GCI_AlphaBand);
3696 : }
3697 17 : else if (GDALGetRasterCount(hSrcDS) &&
3698 17 : GDALGetRasterColorInterpretation(GDALGetRasterBand(
3699 : hSrcDS, GDALGetRasterCount(hSrcDS))) ==
3700 34 : GCI_AlphaBand &&
3701 1 : !psOptions->bDisableSrcAlpha)
3702 : {
3703 0 : nDstBandCount++;
3704 0 : apeColorInterpretations.push_back(GCI_AlphaBand);
3705 : }
3706 : }
3707 : }
3708 :
3709 : /* --------------------------------------------------------------------
3710 : */
3711 : /* If we are processing the first file, get the source srs from */
3712 : /* dataset, if not set already. */
3713 : /* --------------------------------------------------------------------
3714 : */
3715 1094 : const auto osThisSourceSRS = GetSrcDSProjection(hSrcDS, papszTO);
3716 1094 : if (iSrc == 0 && osThisTargetSRS.empty())
3717 : {
3718 780 : if (!osThisSourceSRS.empty())
3719 : {
3720 580 : osThisTargetSRS = osThisSourceSRS;
3721 580 : aoTOList.SetNameValue("DST_SRS", osThisSourceSRS);
3722 : }
3723 : }
3724 :
3725 : /* --------------------------------------------------------------------
3726 : */
3727 : /* Create a transformation object from the source to */
3728 : /* destination coordinate system. */
3729 : /* --------------------------------------------------------------------
3730 : */
3731 0 : GDALTransformerArgUniquePtr hTransformArg;
3732 1094 : if (hUniqueTransformArg)
3733 2 : hTransformArg = std::move(hUniqueTransformArg);
3734 : else
3735 : {
3736 1092 : hTransformArg.reset(GDALCreateGenImgProjTransformer2(
3737 1092 : hSrcDS, nullptr, aoTOList.List()));
3738 1092 : if (hTransformArg == nullptr)
3739 : {
3740 7 : return nullptr;
3741 : }
3742 : }
3743 :
3744 : GDALTransformerInfo *psInfo =
3745 1087 : static_cast<GDALTransformerInfo *>(hTransformArg.get());
3746 :
3747 : /* --------------------------------------------------------------------
3748 : */
3749 : /* Get approximate output resolution */
3750 : /* --------------------------------------------------------------------
3751 : */
3752 :
3753 1087 : if (bKnownTargetExtentButNotResolution)
3754 : {
3755 : // Sample points along a grid in target CRS
3756 84 : constexpr int nPointsX = 10;
3757 84 : constexpr int nPointsY = 10;
3758 84 : constexpr int nPoints = 3 * nPointsX * nPointsY;
3759 168 : std::vector<double> padfX;
3760 168 : std::vector<double> padfY;
3761 168 : std::vector<double> padfZ(nPoints);
3762 168 : std::vector<int> pabSuccess(nPoints);
3763 : const double dfEps =
3764 168 : std::min(psOptions->dfMaxX - psOptions->dfMinX,
3765 84 : std::abs(psOptions->dfMaxY - psOptions->dfMinY)) /
3766 84 : 1000;
3767 924 : for (int iY = 0; iY < nPointsY; iY++)
3768 : {
3769 9240 : for (int iX = 0; iX < nPointsX; iX++)
3770 : {
3771 8400 : const double dfX =
3772 8400 : psOptions->dfMinX +
3773 8400 : static_cast<double>(iX) *
3774 8400 : (psOptions->dfMaxX - psOptions->dfMinX) /
3775 : (nPointsX - 1);
3776 8400 : const double dfY =
3777 8400 : psOptions->dfMinY +
3778 8400 : static_cast<double>(iY) *
3779 8400 : (psOptions->dfMaxY - psOptions->dfMinY) /
3780 : (nPointsY - 1);
3781 :
3782 : // Reproject each destination sample point and its
3783 : // neighbours at (x+1,y) and (x,y+1), so as to get the local
3784 : // scale.
3785 8400 : padfX.push_back(dfX);
3786 8400 : padfY.push_back(dfY);
3787 :
3788 15960 : padfX.push_back((iX == nPointsX - 1) ? dfX - dfEps
3789 7560 : : dfX + dfEps);
3790 8400 : padfY.push_back(dfY);
3791 :
3792 8400 : padfX.push_back(dfX);
3793 15960 : padfY.push_back((iY == nPointsY - 1) ? dfY - dfEps
3794 7560 : : dfY + dfEps);
3795 : }
3796 : }
3797 :
3798 84 : bool transformedToSrcCRS{false};
3799 :
3800 : GDALGenImgProjTransformInfo *psTransformInfo{
3801 : static_cast<GDALGenImgProjTransformInfo *>(
3802 84 : hTransformArg.get())};
3803 :
3804 : // If a transformer is available, use an extent that covers the
3805 : // target extent instead of the real source image extent, but also
3806 : // check for target extent compatibility with source CRS extent
3807 84 : if (psTransformInfo && psTransformInfo->pReprojectArg &&
3808 64 : psTransformInfo->sSrcParams.pTransformer == nullptr)
3809 : {
3810 64 : const GDALReprojectionTransformInfo *psRTI =
3811 : static_cast<const GDALReprojectionTransformInfo *>(
3812 : psTransformInfo->pReprojectArg);
3813 64 : if (psRTI->poReverseTransform)
3814 : {
3815 :
3816 : // Compute new geotransform from transformed target extent
3817 : double adfGeoTransform[6];
3818 64 : if (GDALGetGeoTransform(hSrcDS, adfGeoTransform) ==
3819 64 : CE_None &&
3820 64 : adfGeoTransform[2] == 0 && adfGeoTransform[4] == 0)
3821 : {
3822 :
3823 : // Transform target extent to source CRS
3824 64 : double dfMinX = psOptions->dfMinX;
3825 64 : double dfMinY = psOptions->dfMinY;
3826 :
3827 : // Need this to check if the target extent is compatible with the source extent
3828 64 : double dfMaxX = psOptions->dfMaxX;
3829 64 : double dfMaxY = psOptions->dfMaxY;
3830 :
3831 : // Clone of psRTI->poReverseTransform with CHECK_WITH_INVERT_PROJ set to TRUE
3832 : // to detect out of source CRS bounds destination extent and fall back to original
3833 : // algorithm if needed
3834 : CPLConfigOptionSetter oSetter("CHECK_WITH_INVERT_PROJ",
3835 128 : "TRUE", false);
3836 128 : OGRCoordinateTransformationOptions options;
3837 : auto poReverseTransform =
3838 : std::unique_ptr<OGRCoordinateTransformation>(
3839 : OGRCreateCoordinateTransformation(
3840 64 : psRTI->poReverseTransform->GetSourceCS(),
3841 64 : psRTI->poReverseTransform->GetTargetCS(),
3842 128 : options));
3843 :
3844 64 : if (poReverseTransform)
3845 : {
3846 :
3847 128 : poReverseTransform->Transform(
3848 64 : 1, &dfMinX, &dfMinY, nullptr, &pabSuccess[0]);
3849 :
3850 64 : if (pabSuccess[0])
3851 : {
3852 60 : adfGeoTransform[0] = dfMinX;
3853 60 : adfGeoTransform[3] = dfMinY;
3854 :
3855 120 : poReverseTransform->Transform(1, &dfMaxX,
3856 : &dfMaxY, nullptr,
3857 60 : &pabSuccess[0]);
3858 :
3859 60 : if (pabSuccess[0])
3860 : {
3861 :
3862 : // Reproject to source image CRS
3863 59 : psRTI->poReverseTransform->Transform(
3864 59 : nPoints, &padfX[0], &padfY[0],
3865 59 : &padfZ[0], &pabSuccess[0]);
3866 :
3867 : // Transform back to source image coordinate space using geotransform
3868 17759 : for (size_t i = 0; i < padfX.size(); i++)
3869 : {
3870 35400 : padfX[i] =
3871 17700 : (padfX[i] - adfGeoTransform[0]) /
3872 17700 : adfGeoTransform[1];
3873 17700 : padfY[i] = std::abs(
3874 17700 : (padfY[i] - adfGeoTransform[3]) /
3875 17700 : adfGeoTransform[5]);
3876 : }
3877 :
3878 59 : transformedToSrcCRS = true;
3879 : }
3880 : }
3881 : }
3882 : }
3883 : }
3884 : }
3885 :
3886 84 : if (!transformedToSrcCRS)
3887 : {
3888 : // Transform to source image coordinate space
3889 25 : psInfo->pfnTransform(hTransformArg.get(), TRUE, nPoints,
3890 25 : &padfX[0], &padfY[0], &padfZ[0],
3891 25 : &pabSuccess[0]);
3892 : }
3893 :
3894 : // Compute the resolution at sampling points
3895 168 : std::vector<std::pair<double, double>> aoResPairs;
3896 :
3897 15062 : const auto Distance = [](double x, double y)
3898 15062 : { return sqrt(x * x + y * y); };
3899 :
3900 84 : const int nSrcXSize = GDALGetRasterXSize(hSrcDS);
3901 84 : const int nSrcYSize = GDALGetRasterYSize(hSrcDS);
3902 :
3903 8484 : for (int i = 0; i < nPoints; i += 3)
3904 : {
3905 16800 : if (pabSuccess[i] && pabSuccess[i + 1] && pabSuccess[i + 2] &&
3906 18925 : padfX[i] >= 0 && padfY[i] >= 0 &&
3907 2125 : (transformedToSrcCRS ||
3908 2125 : (padfX[i] <= nSrcXSize && padfY[i] <= nSrcYSize)))
3909 : {
3910 : const double dfRes1 =
3911 15062 : std::abs(dfEps) / Distance(padfX[i + 1] - padfX[i],
3912 7531 : padfY[i + 1] - padfY[i]);
3913 : const double dfRes2 =
3914 15062 : std::abs(dfEps) / Distance(padfX[i + 2] - padfX[i],
3915 7531 : padfY[i + 2] - padfY[i]);
3916 7531 : if (std::isfinite(dfRes1) && std::isfinite(dfRes2))
3917 : {
3918 7511 : aoResPairs.push_back(
3919 15022 : std::pair<double, double>(dfRes1, dfRes2));
3920 : }
3921 : }
3922 : }
3923 :
3924 : // Find the minimum resolution that is at least 10 times greater
3925 : // than the median, to remove outliers.
3926 : // Start first by doing that on dfRes1, then dfRes2 and then their
3927 : // average.
3928 84 : std::sort(aoResPairs.begin(), aoResPairs.end(),
3929 38467 : [](const std::pair<double, double> &oPair1,
3930 : const std::pair<double, double> &oPair2)
3931 38467 : { return oPair1.first < oPair2.first; });
3932 :
3933 84 : if (!aoResPairs.empty())
3934 : {
3935 168 : std::vector<std::pair<double, double>> aoResPairsNew;
3936 : const double dfMedian1 =
3937 84 : aoResPairs[aoResPairs.size() / 2].first;
3938 7595 : for (const auto &oPair : aoResPairs)
3939 : {
3940 7511 : if (oPair.first > dfMedian1 / 10)
3941 : {
3942 7481 : aoResPairsNew.push_back(oPair);
3943 : }
3944 : }
3945 :
3946 84 : aoResPairs = std::move(aoResPairsNew);
3947 84 : std::sort(aoResPairs.begin(), aoResPairs.end(),
3948 40580 : [](const std::pair<double, double> &oPair1,
3949 : const std::pair<double, double> &oPair2)
3950 40580 : { return oPair1.second < oPair2.second; });
3951 84 : if (!aoResPairs.empty())
3952 : {
3953 168 : std::vector<double> adfRes;
3954 : const double dfMedian2 =
3955 84 : aoResPairs[aoResPairs.size() / 2].second;
3956 7565 : for (const auto &oPair : aoResPairs)
3957 : {
3958 7481 : if (oPair.second > dfMedian2 / 10)
3959 : {
3960 7471 : adfRes.push_back((oPair.first + oPair.second) / 2);
3961 : }
3962 : }
3963 :
3964 84 : std::sort(adfRes.begin(), adfRes.end());
3965 84 : if (!adfRes.empty())
3966 : {
3967 84 : const double dfMedian = adfRes[adfRes.size() / 2];
3968 84 : for (const double dfRes : adfRes)
3969 : {
3970 84 : if (dfRes > dfMedian / 10)
3971 : {
3972 84 : dfResFromSourceAndTargetExtent = std::min(
3973 84 : dfResFromSourceAndTargetExtent, dfRes);
3974 84 : break;
3975 : }
3976 : }
3977 : }
3978 : }
3979 : }
3980 : }
3981 :
3982 : /* --------------------------------------------------------------------
3983 : */
3984 : /* Get approximate output definition. */
3985 : /* --------------------------------------------------------------------
3986 : */
3987 : double adfThisGeoTransform[6];
3988 : double adfExtent[4];
3989 1087 : if (bNeedsSuggestedWarpOutput)
3990 : {
3991 : int nThisPixels, nThisLines;
3992 :
3993 : // For sum, round-up dimension, to be sure that the output extent
3994 : // includes all source pixels, to have the sum preserving property.
3995 1914 : int nOptions = (psOptions->eResampleAlg == GRA_Sum)
3996 957 : ? GDAL_SWO_ROUND_UP_SIZE
3997 : : 0;
3998 957 : if (psOptions->bSquarePixels)
3999 : {
4000 1 : nOptions |= GDAL_SWO_FORCE_SQUARE_PIXEL;
4001 : }
4002 :
4003 957 : if (GDALSuggestedWarpOutput2(
4004 : hSrcDS, psInfo->pfnTransform, hTransformArg.get(),
4005 : adfThisGeoTransform, &nThisPixels, &nThisLines, adfExtent,
4006 957 : nOptions) != CE_None)
4007 : {
4008 0 : return nullptr;
4009 : }
4010 :
4011 957 : if (CPLGetConfigOption("CHECK_WITH_INVERT_PROJ", nullptr) ==
4012 : nullptr)
4013 : {
4014 957 : double MinX = adfExtent[0];
4015 957 : double MaxX = adfExtent[2];
4016 957 : double MaxY = adfExtent[3];
4017 957 : double MinY = adfExtent[1];
4018 957 : int bSuccess = TRUE;
4019 :
4020 : // +/-180 deg in longitude do not roundtrip sometimes
4021 957 : if (MinX == -180)
4022 51 : MinX += 1e-6;
4023 957 : if (MaxX == 180)
4024 50 : MaxX -= 1e-6;
4025 :
4026 : // +/-90 deg in latitude do not roundtrip sometimes
4027 957 : if (MinY == -90)
4028 52 : MinY += 1e-6;
4029 957 : if (MaxY == 90)
4030 54 : MaxY -= 1e-6;
4031 :
4032 : /* Check that the edges of the target image are in the validity
4033 : * area */
4034 : /* of the target projection */
4035 957 : const int N_STEPS = 20;
4036 20449 : for (int i = 0; i <= N_STEPS && bSuccess; i++)
4037 : {
4038 428308 : for (int j = 0; j <= N_STEPS && bSuccess; j++)
4039 : {
4040 408816 : const double dfRatioI = i * 1.0 / N_STEPS;
4041 408816 : const double dfRatioJ = j * 1.0 / N_STEPS;
4042 408816 : const double expected_x =
4043 408816 : (1 - dfRatioI) * MinX + dfRatioI * MaxX;
4044 408816 : const double expected_y =
4045 408816 : (1 - dfRatioJ) * MinY + dfRatioJ * MaxY;
4046 408816 : double x = expected_x;
4047 408816 : double y = expected_y;
4048 408816 : double z = 0;
4049 : /* Target SRS coordinates to source image pixel
4050 : * coordinates */
4051 408816 : if (!psInfo->pfnTransform(hTransformArg.get(), TRUE, 1,
4052 817612 : &x, &y, &z, &bSuccess) ||
4053 408796 : !bSuccess)
4054 20 : bSuccess = FALSE;
4055 : /* Source image pixel coordinates to target SRS
4056 : * coordinates */
4057 408816 : if (!psInfo->pfnTransform(hTransformArg.get(), FALSE, 1,
4058 817610 : &x, &y, &z, &bSuccess) ||
4059 408794 : !bSuccess)
4060 22 : bSuccess = FALSE;
4061 408816 : if (fabs(x - expected_x) >
4062 408816 : (MaxX - MinX) / nThisPixels ||
4063 408785 : fabs(y - expected_y) > (MaxY - MinY) / nThisLines)
4064 31 : bSuccess = FALSE;
4065 : }
4066 : }
4067 :
4068 : /* If not, retry with CHECK_WITH_INVERT_PROJ=TRUE that forces
4069 : * ogrct.cpp */
4070 : /* to check the consistency of each requested projection result
4071 : * with the */
4072 : /* invert projection */
4073 957 : if (!bSuccess)
4074 : {
4075 31 : CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ",
4076 : "TRUE");
4077 31 : CPLDebug("WARP", "Recompute out extent with "
4078 : "CHECK_WITH_INVERT_PROJ=TRUE");
4079 :
4080 31 : const CPLErr eErr = GDALSuggestedWarpOutput2(
4081 : hSrcDS, psInfo->pfnTransform, hTransformArg.get(),
4082 : adfThisGeoTransform, &nThisPixels, &nThisLines,
4083 : adfExtent, 0);
4084 31 : CPLSetThreadLocalConfigOption("CHECK_WITH_INVERT_PROJ",
4085 : nullptr);
4086 31 : if (eErr != CE_None)
4087 : {
4088 0 : return nullptr;
4089 : }
4090 : }
4091 : }
4092 : }
4093 :
4094 : // If no reprojection or geometry change is involved, and that the
4095 : // source image is north-up, preserve source resolution instead of
4096 : // forcing square pixels.
4097 1087 : const char *pszMethod = FetchSrcMethod(papszTO);
4098 : double adfThisGeoTransformTmp[6];
4099 1086 : if (!psOptions->bSquarePixels && bNeedsSuggestedWarpOutput &&
4100 956 : psOptions->dfXRes == 0 && psOptions->dfYRes == 0 &&
4101 928 : psOptions->nForcePixels == 0 && psOptions->nForceLines == 0 &&
4102 32 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")) &&
4103 785 : CSLFetchNameValue(papszTO, "COORDINATE_OPERATION") == nullptr &&
4104 781 : CSLFetchNameValue(papszTO, "SRC_METHOD") == nullptr &&
4105 781 : CSLFetchNameValue(papszTO, "DST_METHOD") == nullptr &&
4106 778 : GDALGetGeoTransform(hSrcDS, adfThisGeoTransformTmp) == CE_None &&
4107 758 : adfThisGeoTransformTmp[2] == 0 && adfThisGeoTransformTmp[4] == 0 &&
4108 756 : adfThisGeoTransformTmp[5] < 0 &&
4109 2815 : GDALGetMetadata(hSrcDS, "GEOLOC_ARRAY") == nullptr &&
4110 642 : GDALGetMetadata(hSrcDS, GDAL_MDD_RPC) == nullptr)
4111 : {
4112 642 : bool bIsSameHorizontal = osThisSourceSRS == osThisTargetSRS;
4113 642 : if (!bIsSameHorizontal)
4114 : {
4115 328 : OGRSpatialReference oSrcSRS;
4116 328 : OGRSpatialReference oDstSRS;
4117 328 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
4118 164 : if (oSrcSRS.SetFromUserInput(osThisSourceSRS.c_str()) ==
4119 164 : OGRERR_NONE &&
4120 164 : oDstSRS.SetFromUserInput(osThisTargetSRS.c_str()) ==
4121 164 : OGRERR_NONE &&
4122 164 : (oSrcSRS.GetAxesCount() == 3 ||
4123 146 : oDstSRS.GetAxesCount() == 3) &&
4124 350 : oSrcSRS.DemoteTo2D(nullptr) == OGRERR_NONE &&
4125 22 : oDstSRS.DemoteTo2D(nullptr) == OGRERR_NONE)
4126 : {
4127 22 : bIsSameHorizontal = CPL_TO_BOOL(oSrcSRS.IsSame(&oDstSRS));
4128 : }
4129 : }
4130 642 : if (bIsSameHorizontal)
4131 : {
4132 484 : memcpy(adfThisGeoTransform, adfThisGeoTransformTmp,
4133 : 6 * sizeof(double));
4134 484 : adfExtent[0] = adfThisGeoTransform[0];
4135 484 : adfExtent[1] =
4136 968 : adfThisGeoTransform[3] +
4137 484 : GDALGetRasterYSize(hSrcDS) * adfThisGeoTransform[5];
4138 484 : adfExtent[2] =
4139 968 : adfThisGeoTransform[0] +
4140 484 : GDALGetRasterXSize(hSrcDS) * adfThisGeoTransform[1];
4141 484 : adfExtent[3] = adfThisGeoTransform[3];
4142 484 : dfResFromSourceAndTargetExtent =
4143 484 : std::numeric_limits<double>::infinity();
4144 : }
4145 : }
4146 :
4147 1087 : if (bNeedsSuggestedWarpOutput)
4148 : {
4149 : /* --------------------------------------------------------------------
4150 : */
4151 : /* Expand the working bounds to include this region, ensure the
4152 : */
4153 : /* working resolution is no more than this resolution. */
4154 : /* --------------------------------------------------------------------
4155 : */
4156 957 : if (dfWrkMaxX == 0.0 && dfWrkMinX == 0.0)
4157 : {
4158 933 : dfWrkMinX = adfExtent[0];
4159 933 : dfWrkMaxX = adfExtent[2];
4160 933 : dfWrkMaxY = adfExtent[3];
4161 933 : dfWrkMinY = adfExtent[1];
4162 933 : dfWrkResX = adfThisGeoTransform[1];
4163 933 : dfWrkResY = std::abs(adfThisGeoTransform[5]);
4164 : }
4165 : else
4166 : {
4167 24 : dfWrkMinX = std::min(dfWrkMinX, adfExtent[0]);
4168 24 : dfWrkMaxX = std::max(dfWrkMaxX, adfExtent[2]);
4169 24 : dfWrkMaxY = std::max(dfWrkMaxY, adfExtent[3]);
4170 24 : dfWrkMinY = std::min(dfWrkMinY, adfExtent[1]);
4171 24 : dfWrkResX = std::min(dfWrkResX, adfThisGeoTransform[1]);
4172 24 : dfWrkResY =
4173 24 : std::min(dfWrkResY, std::abs(adfThisGeoTransform[5]));
4174 : }
4175 : }
4176 :
4177 1087 : if (nSrcCount == 1)
4178 : {
4179 1035 : hUniqueTransformArg = std::move(hTransformArg);
4180 : }
4181 : }
4182 :
4183 : // If the source file(s) and the dest one share some files in common,
4184 : // only remove the files that are *not* in common
4185 1063 : if (!oSetExistingDestFilesFoundInSource.empty())
4186 : {
4187 14 : for (const std::string &osFilename : oSetExistingDestFiles)
4188 : {
4189 9 : if (oSetExistingDestFilesFoundInSource.find(osFilename) ==
4190 18 : oSetExistingDestFilesFoundInSource.end())
4191 : {
4192 4 : VSIUnlink(osFilename.c_str());
4193 : }
4194 : }
4195 : }
4196 :
4197 1063 : if (std::isfinite(dfResFromSourceAndTargetExtent))
4198 : {
4199 26 : dfWrkResX = dfResFromSourceAndTargetExtent;
4200 26 : dfWrkResY = dfResFromSourceAndTargetExtent;
4201 : }
4202 :
4203 : /* -------------------------------------------------------------------- */
4204 : /* Did we have any usable sources? */
4205 : /* -------------------------------------------------------------------- */
4206 1063 : if (nDstBandCount == 0)
4207 : {
4208 3 : CPLError(CE_Failure, CPLE_AppDefined, "No usable source images.");
4209 3 : return nullptr;
4210 : }
4211 :
4212 : /* -------------------------------------------------------------------- */
4213 : /* Turn the suggested region into a geotransform and suggested */
4214 : /* number of pixels and lines. */
4215 : /* -------------------------------------------------------------------- */
4216 1060 : double adfDstGeoTransform[6] = {0, 0, 0, 0, 0, 0};
4217 1060 : int nPixels = 0;
4218 1060 : int nLines = 0;
4219 :
4220 200 : const auto ComputePixelsFromResAndExtent = [psOptions]()
4221 : {
4222 400 : return std::max(1.0,
4223 400 : std::round((psOptions->dfMaxX - psOptions->dfMinX) /
4224 200 : psOptions->dfXRes));
4225 1060 : };
4226 :
4227 202 : const auto ComputeLinesFromResAndExtent = [psOptions]()
4228 : {
4229 : return std::max(
4230 404 : 1.0, std::round(std::fabs(psOptions->dfMaxY - psOptions->dfMinY) /
4231 202 : psOptions->dfYRes));
4232 1060 : };
4233 :
4234 1060 : if (bNeedsSuggestedWarpOutput)
4235 : {
4236 931 : adfDstGeoTransform[0] = dfWrkMinX;
4237 931 : adfDstGeoTransform[1] = dfWrkResX;
4238 931 : adfDstGeoTransform[2] = 0.0;
4239 931 : adfDstGeoTransform[3] = dfWrkMaxY;
4240 931 : adfDstGeoTransform[4] = 0.0;
4241 931 : adfDstGeoTransform[5] = -1 * dfWrkResY;
4242 :
4243 931 : const double dfPixels = (dfWrkMaxX - dfWrkMinX) / dfWrkResX;
4244 931 : const double dfLines = (dfWrkMaxY - dfWrkMinY) / dfWrkResY;
4245 : // guaranteed by GDALSuggestedWarpOutput2() behavior
4246 931 : CPLAssert(std::round(dfPixels) <= INT_MAX);
4247 931 : CPLAssert(std::round(dfLines) <= INT_MAX);
4248 931 : nPixels =
4249 931 : static_cast<int>(std::min<double>(std::round(dfPixels), INT_MAX));
4250 931 : nLines =
4251 931 : static_cast<int>(std::min<double>(std::round(dfLines), INT_MAX));
4252 : }
4253 :
4254 : /* -------------------------------------------------------------------- */
4255 : /* Did the user override some parameters? */
4256 : /* -------------------------------------------------------------------- */
4257 1060 : if (UseTEAndTSAndTRConsistently(psOptions))
4258 : {
4259 26 : adfDstGeoTransform[0] = psOptions->dfMinX;
4260 26 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4261 26 : adfDstGeoTransform[1] = psOptions->dfXRes;
4262 26 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4263 :
4264 26 : nPixels = psOptions->nForcePixels;
4265 26 : nLines = psOptions->nForceLines;
4266 : }
4267 1034 : else if (psOptions->dfXRes != 0.0 && psOptions->dfYRes != 0.0)
4268 : {
4269 93 : bool bDetectBlankBorders = false;
4270 :
4271 93 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4272 29 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4273 : {
4274 28 : bDetectBlankBorders = bNeedsSuggestedWarpOutput;
4275 :
4276 28 : psOptions->dfMinX = adfDstGeoTransform[0];
4277 28 : psOptions->dfMaxX =
4278 28 : adfDstGeoTransform[0] + adfDstGeoTransform[1] * nPixels;
4279 28 : psOptions->dfMaxY = adfDstGeoTransform[3];
4280 28 : psOptions->dfMinY =
4281 28 : adfDstGeoTransform[3] + adfDstGeoTransform[5] * nLines;
4282 : }
4283 :
4284 182 : if (psOptions->bTargetAlignedPixels ||
4285 89 : (psOptions->bCropToCutline &&
4286 1 : psOptions->aosWarpOptions.FetchBool("CUTLINE_ALL_TOUCHED", false)))
4287 : {
4288 4 : if ((psOptions->bTargetAlignedPixels &&
4289 10 : bNeedsSuggestedWarpOutput) ||
4290 2 : (psOptions->bCropToCutline &&
4291 1 : psOptions->aosWarpOptions.FetchBool("CUTLINE_ALL_TOUCHED",
4292 : false)))
4293 : {
4294 4 : bDetectBlankBorders = true;
4295 : }
4296 5 : constexpr double EPS = 1e-8;
4297 5 : psOptions->dfMinX =
4298 5 : floor(psOptions->dfMinX / psOptions->dfXRes + EPS) *
4299 5 : psOptions->dfXRes;
4300 5 : psOptions->dfMaxX =
4301 5 : ceil(psOptions->dfMaxX / psOptions->dfXRes - EPS) *
4302 5 : psOptions->dfXRes;
4303 5 : psOptions->dfMinY =
4304 5 : floor(psOptions->dfMinY / psOptions->dfYRes + EPS) *
4305 5 : psOptions->dfYRes;
4306 5 : psOptions->dfMaxY =
4307 5 : ceil(psOptions->dfMaxY / psOptions->dfYRes - EPS) *
4308 5 : psOptions->dfYRes;
4309 : }
4310 :
4311 120 : const auto UpdateGeoTransformandAndPixelLines = [&]()
4312 : {
4313 120 : const double dfPixels = ComputePixelsFromResAndExtent();
4314 120 : const double dfLines = ComputeLinesFromResAndExtent();
4315 120 : if (dfPixels > INT_MAX || dfLines > INT_MAX)
4316 : {
4317 2 : CPLError(CE_Failure, CPLE_AppDefined,
4318 : "Too large output raster size: %f x %f", dfPixels,
4319 : dfLines);
4320 2 : return false;
4321 : }
4322 :
4323 118 : nPixels = static_cast<int>(dfPixels);
4324 118 : nLines = static_cast<int>(dfLines);
4325 354 : adfDstGeoTransform[0] = psOptions->dfMinX;
4326 118 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4327 118 : adfDstGeoTransform[1] = psOptions->dfXRes;
4328 236 : adfDstGeoTransform[5] = (psOptions->dfMaxY > psOptions->dfMinY)
4329 118 : ? -psOptions->dfYRes
4330 2 : : psOptions->dfYRes;
4331 118 : return true;
4332 93 : };
4333 :
4334 122 : if (bDetectBlankBorders && nSrcCount == 1 && hUniqueTransformArg &&
4335 : // to avoid too large memory allocations
4336 29 : std::max(nPixels, nLines) < 100 * 1000 * 1000)
4337 : {
4338 : // Try to detect if the edge of the raster would be blank
4339 : // Cf https://github.com/OSGeo/gdal/issues/7905
4340 30 : while (nPixels > 1 || nLines > 1)
4341 : {
4342 27 : if (!UpdateGeoTransformandAndPixelLines())
4343 0 : return nullptr;
4344 :
4345 27 : GDALSetGenImgProjTransformerDstGeoTransform(
4346 : hUniqueTransformArg.get(), adfDstGeoTransform);
4347 :
4348 27 : std::vector<double> adfX(std::max(nPixels, nLines));
4349 27 : std::vector<double> adfY(adfX.size());
4350 27 : std::vector<double> adfZ(adfX.size());
4351 27 : std::vector<int> abSuccess(adfX.size());
4352 :
4353 : const auto DetectBlankBorder =
4354 108 : [&](int nValues,
4355 : std::function<bool(double, double)> funcIsOK)
4356 : {
4357 108 : if (nValues > 3)
4358 : {
4359 : // First try with just a subsample of 3 points
4360 52 : double adf3X[3] = {adfX[0], adfX[nValues / 2],
4361 52 : adfX[nValues - 1]};
4362 52 : double adf3Y[3] = {adfY[0], adfY[nValues / 2],
4363 52 : adfY[nValues - 1]};
4364 52 : double adf3Z[3] = {0};
4365 52 : GDALGenImgProjTransform(hUniqueTransformArg.get(), TRUE,
4366 : 3, &adf3X[0], &adf3Y[0],
4367 52 : &adf3Z[0], &abSuccess[0]);
4368 66 : for (int i = 0; i < 3; ++i)
4369 : {
4370 63 : if (abSuccess[i] && funcIsOK(adf3X[i], adf3Y[i]))
4371 : {
4372 49 : return false;
4373 : }
4374 : }
4375 : }
4376 :
4377 : // Do on full border to confirm
4378 59 : GDALGenImgProjTransform(hUniqueTransformArg.get(), TRUE,
4379 59 : nValues, &adfX[0], &adfY[0],
4380 59 : &adfZ[0], &abSuccess[0]);
4381 125 : for (int i = 0; i < nValues; ++i)
4382 : {
4383 122 : if (abSuccess[i] && funcIsOK(adfX[i], adfY[i]))
4384 : {
4385 56 : return false;
4386 : }
4387 : }
4388 :
4389 3 : return true;
4390 27 : };
4391 :
4392 9912 : for (int i = 0; i < nPixels; ++i)
4393 : {
4394 9885 : adfX[i] = i + 0.5;
4395 9885 : adfY[i] = 0.5;
4396 9885 : adfZ[i] = 0;
4397 : }
4398 27 : const bool bTopBlankLine = DetectBlankBorder(
4399 42 : nPixels, [](double, double y) { return y >= 0; });
4400 :
4401 9912 : for (int i = 0; i < nPixels; ++i)
4402 : {
4403 9885 : adfX[i] = i + 0.5;
4404 9885 : adfY[i] = nLines - 0.5;
4405 9885 : adfZ[i] = 0;
4406 : }
4407 27 : const int nSrcLines = GDALGetRasterYSize(pahSrcDS[0]);
4408 : const bool bBottomBlankLine =
4409 27 : DetectBlankBorder(nPixels, [nSrcLines](double, double y)
4410 27 : { return y <= nSrcLines; });
4411 :
4412 5139 : for (int i = 0; i < nLines; ++i)
4413 : {
4414 5112 : adfX[i] = 0.5;
4415 5112 : adfY[i] = i + 0.5;
4416 5112 : adfZ[i] = 0;
4417 : }
4418 27 : const bool bLeftBlankCol = DetectBlankBorder(
4419 58 : nLines, [](double x, double) { return x >= 0; });
4420 :
4421 5139 : for (int i = 0; i < nLines; ++i)
4422 : {
4423 5112 : adfX[i] = nPixels - 0.5;
4424 5112 : adfY[i] = i + 0.5;
4425 5112 : adfZ[i] = 0;
4426 : }
4427 27 : const int nSrcCols = GDALGetRasterXSize(pahSrcDS[0]);
4428 : const bool bRightBlankCol =
4429 27 : DetectBlankBorder(nLines, [nSrcCols](double x, double)
4430 54 : { return x <= nSrcCols; });
4431 :
4432 27 : if (!bTopBlankLine && !bBottomBlankLine && !bLeftBlankCol &&
4433 25 : !bRightBlankCol)
4434 25 : break;
4435 :
4436 2 : if (bTopBlankLine)
4437 : {
4438 1 : if (psOptions->dfMaxY - psOptions->dfMinY <=
4439 1 : 2 * psOptions->dfYRes)
4440 0 : break;
4441 1 : psOptions->dfMaxY -= psOptions->dfYRes;
4442 : }
4443 2 : if (bBottomBlankLine)
4444 : {
4445 0 : if (psOptions->dfMaxY - psOptions->dfMinY <=
4446 0 : 2 * psOptions->dfYRes)
4447 0 : break;
4448 0 : psOptions->dfMinY += psOptions->dfYRes;
4449 : }
4450 2 : if (bLeftBlankCol)
4451 : {
4452 1 : if (psOptions->dfMaxX - psOptions->dfMinX <=
4453 1 : 2 * psOptions->dfXRes)
4454 0 : break;
4455 1 : psOptions->dfMinX += psOptions->dfXRes;
4456 : }
4457 2 : if (bRightBlankCol)
4458 : {
4459 1 : if (psOptions->dfMaxX - psOptions->dfMinX <=
4460 1 : 2 * psOptions->dfXRes)
4461 0 : break;
4462 1 : psOptions->dfMaxX -= psOptions->dfXRes;
4463 : }
4464 : }
4465 : }
4466 :
4467 93 : if (!UpdateGeoTransformandAndPixelLines())
4468 93 : return nullptr;
4469 : }
4470 :
4471 941 : else if (psOptions->nForcePixels != 0 && psOptions->nForceLines != 0)
4472 : {
4473 142 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4474 104 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4475 : {
4476 104 : psOptions->dfMinX = dfWrkMinX;
4477 104 : psOptions->dfMaxX = dfWrkMaxX;
4478 104 : psOptions->dfMaxY = dfWrkMaxY;
4479 104 : psOptions->dfMinY = dfWrkMinY;
4480 : }
4481 :
4482 142 : psOptions->dfXRes =
4483 142 : (psOptions->dfMaxX - psOptions->dfMinX) / psOptions->nForcePixels;
4484 142 : psOptions->dfYRes =
4485 142 : (psOptions->dfMaxY - psOptions->dfMinY) / psOptions->nForceLines;
4486 :
4487 142 : adfDstGeoTransform[0] = psOptions->dfMinX;
4488 142 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4489 142 : adfDstGeoTransform[1] = psOptions->dfXRes;
4490 142 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4491 :
4492 142 : nPixels = psOptions->nForcePixels;
4493 142 : nLines = psOptions->nForceLines;
4494 : }
4495 :
4496 799 : else if (psOptions->nForcePixels != 0)
4497 : {
4498 5 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4499 4 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4500 : {
4501 4 : psOptions->dfMinX = dfWrkMinX;
4502 4 : psOptions->dfMaxX = dfWrkMaxX;
4503 4 : psOptions->dfMaxY = dfWrkMaxY;
4504 4 : psOptions->dfMinY = dfWrkMinY;
4505 : }
4506 :
4507 5 : psOptions->dfXRes =
4508 5 : (psOptions->dfMaxX - psOptions->dfMinX) / psOptions->nForcePixels;
4509 5 : psOptions->dfYRes = psOptions->dfXRes;
4510 :
4511 5 : adfDstGeoTransform[0] = psOptions->dfMinX;
4512 5 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4513 5 : adfDstGeoTransform[1] = psOptions->dfXRes;
4514 10 : adfDstGeoTransform[5] = (psOptions->dfMaxY > psOptions->dfMinY)
4515 5 : ? -psOptions->dfYRes
4516 0 : : psOptions->dfYRes;
4517 :
4518 5 : nPixels = psOptions->nForcePixels;
4519 5 : const double dfLines = ComputeLinesFromResAndExtent();
4520 5 : if (dfLines > INT_MAX)
4521 : {
4522 1 : CPLError(CE_Failure, CPLE_AppDefined,
4523 : "Too large output raster size: %d x %f", nPixels, dfLines);
4524 1 : return nullptr;
4525 : }
4526 4 : nLines = static_cast<int>(dfLines);
4527 : }
4528 :
4529 794 : else if (psOptions->nForceLines != 0)
4530 : {
4531 3 : if (psOptions->dfMinX == 0.0 && psOptions->dfMinY == 0.0 &&
4532 3 : psOptions->dfMaxX == 0.0 && psOptions->dfMaxY == 0.0)
4533 : {
4534 3 : psOptions->dfMinX = dfWrkMinX;
4535 3 : psOptions->dfMaxX = dfWrkMaxX;
4536 3 : psOptions->dfMaxY = dfWrkMaxY;
4537 3 : psOptions->dfMinY = dfWrkMinY;
4538 : }
4539 :
4540 3 : psOptions->dfYRes =
4541 3 : (psOptions->dfMaxY - psOptions->dfMinY) / psOptions->nForceLines;
4542 3 : psOptions->dfXRes = std::fabs(psOptions->dfYRes);
4543 :
4544 3 : adfDstGeoTransform[0] = psOptions->dfMinX;
4545 3 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4546 3 : adfDstGeoTransform[1] = psOptions->dfXRes;
4547 3 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4548 :
4549 3 : const double dfPixels = ComputePixelsFromResAndExtent();
4550 3 : nLines = psOptions->nForceLines;
4551 3 : if (dfPixels > INT_MAX)
4552 : {
4553 1 : CPLError(CE_Failure, CPLE_AppDefined,
4554 : "Too large output raster size: %f x %d", dfPixels, nLines);
4555 1 : return nullptr;
4556 : }
4557 2 : nPixels = static_cast<int>(dfPixels);
4558 : }
4559 :
4560 791 : else if (psOptions->dfMinX != 0.0 || psOptions->dfMinY != 0.0 ||
4561 715 : psOptions->dfMaxX != 0.0 || psOptions->dfMaxY != 0.0)
4562 : {
4563 77 : psOptions->dfXRes = adfDstGeoTransform[1];
4564 77 : psOptions->dfYRes = fabs(adfDstGeoTransform[5]);
4565 :
4566 77 : const double dfPixels = ComputePixelsFromResAndExtent();
4567 77 : const double dfLines = ComputeLinesFromResAndExtent();
4568 77 : if (dfPixels > INT_MAX || dfLines > INT_MAX)
4569 : {
4570 1 : CPLError(CE_Failure, CPLE_AppDefined,
4571 : "Too large output raster size: %f x %f", dfPixels,
4572 : dfLines);
4573 1 : return nullptr;
4574 : }
4575 :
4576 76 : nPixels = static_cast<int>(dfPixels);
4577 76 : nLines = static_cast<int>(dfLines);
4578 :
4579 76 : psOptions->dfXRes = (psOptions->dfMaxX - psOptions->dfMinX) / nPixels;
4580 76 : psOptions->dfYRes = (psOptions->dfMaxY - psOptions->dfMinY) / nLines;
4581 :
4582 76 : adfDstGeoTransform[0] = psOptions->dfMinX;
4583 76 : adfDstGeoTransform[3] = psOptions->dfMaxY;
4584 76 : adfDstGeoTransform[1] = psOptions->dfXRes;
4585 76 : adfDstGeoTransform[5] = -psOptions->dfYRes;
4586 : }
4587 :
4588 1055 : if (EQUAL(pszFormat, "GTiff"))
4589 : {
4590 :
4591 : /* --------------------------------------------------------------------
4592 : */
4593 : /* Automatically set PHOTOMETRIC=RGB for GTiff when appropriate */
4594 : /* --------------------------------------------------------------------
4595 : */
4596 457 : if (apeColorInterpretations.size() >= 3 &&
4597 35 : apeColorInterpretations[0] == GCI_RedBand &&
4598 35 : apeColorInterpretations[1] == GCI_GreenBand &&
4599 527 : apeColorInterpretations[2] == GCI_BlueBand &&
4600 35 : aosCreateOptions.FetchNameValue("PHOTOMETRIC") == nullptr)
4601 : {
4602 35 : aosCreateOptions.SetNameValue("PHOTOMETRIC", "RGB");
4603 :
4604 : // Preserve potential ALPHA=PREMULTIPLIED from source alpha band
4605 70 : if (aosCreateOptions.FetchNameValue("ALPHA") == nullptr &&
4606 35 : apeColorInterpretations.size() == 4 &&
4607 75 : apeColorInterpretations[3] == GCI_AlphaBand &&
4608 5 : GDALGetRasterCount(pahSrcDS[0]) == 4)
4609 : {
4610 : const char *pszAlpha =
4611 5 : GDALGetMetadataItem(GDALGetRasterBand(pahSrcDS[0], 4),
4612 : "ALPHA", GDAL_MDD_IMAGE_STRUCTURE);
4613 5 : if (pszAlpha)
4614 : {
4615 1 : aosCreateOptions.SetNameValue("ALPHA", pszAlpha);
4616 : }
4617 : }
4618 : }
4619 :
4620 : /* The GTiff driver now supports writing band color interpretation */
4621 : /* in the TIFF_GDAL_METADATA tag */
4622 457 : bSetColorInterpretation = true;
4623 : }
4624 :
4625 : /* -------------------------------------------------------------------- */
4626 : /* Create the output file. */
4627 : /* -------------------------------------------------------------------- */
4628 1055 : if (!psOptions->bQuiet)
4629 78 : printf("Creating output file that is %dP x %dL.\n", nPixels, nLines);
4630 :
4631 1055 : hDstDS = GDALCreate(hDriver, pszFilename, nPixels, nLines, nDstBandCount,
4632 1055 : eDT, aosCreateOptions.List());
4633 :
4634 1055 : if (hDstDS == nullptr)
4635 : {
4636 1 : return nullptr;
4637 : }
4638 :
4639 1054 : if (psOptions->bDeleteOutputFileOnceCreated)
4640 : {
4641 12 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4642 6 : GDALDeleteDataset(hDriver, pszFilename);
4643 : }
4644 :
4645 : /* -------------------------------------------------------------------- */
4646 : /* Write out the projection definition. */
4647 : /* -------------------------------------------------------------------- */
4648 1054 : const char *pszDstMethod = CSLFetchNameValue(papszTO, "DST_METHOD");
4649 1054 : if (pszDstMethod == nullptr || !EQUAL(pszDstMethod, "NO_GEOTRANSFORM"))
4650 : {
4651 1052 : OGRSpatialReference oTargetSRS;
4652 1052 : oTargetSRS.SetFromUserInput(osThisTargetSRS);
4653 1052 : oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
4654 :
4655 1052 : if (oTargetSRS.IsDynamic())
4656 : {
4657 622 : double dfCoordEpoch = CPLAtof(CSLFetchNameValueDef(
4658 : papszTO, "DST_COORDINATE_EPOCH",
4659 : CSLFetchNameValueDef(papszTO, "COORDINATE_EPOCH", "0")));
4660 622 : if (dfCoordEpoch == 0)
4661 : {
4662 : const OGRSpatialReferenceH hSrcSRS =
4663 622 : GDALGetSpatialRef(pahSrcDS[0]);
4664 622 : const char *pszMethod = FetchSrcMethod(papszTO);
4665 622 : if (hSrcSRS &&
4666 7 : (pszMethod == nullptr || EQUAL(pszMethod, "GEOTRANSFORM")))
4667 : {
4668 579 : dfCoordEpoch = OSRGetCoordinateEpoch(hSrcSRS);
4669 : }
4670 : }
4671 622 : if (dfCoordEpoch > 0)
4672 1 : oTargetSRS.SetCoordinateEpoch(dfCoordEpoch);
4673 : }
4674 :
4675 1052 : if (GDALSetSpatialRef(hDstDS, OGRSpatialReference::ToHandle(
4676 2104 : &oTargetSRS)) == CE_Failure ||
4677 1052 : GDALSetGeoTransform(hDstDS, adfDstGeoTransform) == CE_Failure)
4678 : {
4679 0 : GDALClose(hDstDS);
4680 0 : return nullptr;
4681 1052 : }
4682 : }
4683 : else
4684 : {
4685 2 : adfDstGeoTransform[3] += adfDstGeoTransform[5] * nLines;
4686 2 : adfDstGeoTransform[5] = fabs(adfDstGeoTransform[5]);
4687 : }
4688 :
4689 1054 : if (hUniqueTransformArg && bUpdateTransformerWithDestGT)
4690 : {
4691 1028 : GDALSetGenImgProjTransformerDstGeoTransform(hUniqueTransformArg.get(),
4692 : adfDstGeoTransform);
4693 :
4694 1028 : void *pTransformerArg = hUniqueTransformArg.get();
4695 1028 : if (GDALIsTransformer(pTransformerArg,
4696 : GDAL_GEN_IMG_TRANSFORMER_CLASS_NAME))
4697 : {
4698 : // Detect if there is a change of coordinate operation in the area of
4699 : // interest. The underlying proj_trans_get_last_used_operation() is
4700 : // quite costly due to using proj_clone() internally, so only do that
4701 : // on a restricted set of points.
4702 1028 : GDALGenImgProjTransformInfo *psTransformInfo{
4703 : static_cast<GDALGenImgProjTransformInfo *>(pTransformerArg)};
4704 1028 : GDALTransformerInfo *psInfo = &psTransformInfo->sTI;
4705 :
4706 1028 : void *pReprojectArg = psTransformInfo->pReprojectArg;
4707 1028 : if (GDALIsTransformer(pReprojectArg,
4708 : GDAL_APPROX_TRANSFORMER_CLASS_NAME))
4709 : {
4710 1 : const auto *pApproxInfo =
4711 : static_cast<const GDALApproxTransformInfo *>(pReprojectArg);
4712 1 : pReprojectArg = pApproxInfo->pBaseCBData;
4713 : }
4714 :
4715 1028 : if (GDALIsTransformer(pReprojectArg,
4716 : GDAL_REPROJECTION_TRANSFORMER_CLASS_NAME))
4717 : {
4718 620 : const GDALReprojectionTransformInfo *psRTI =
4719 : static_cast<const GDALReprojectionTransformInfo *>(
4720 : pReprojectArg);
4721 620 : if (psRTI->poReverseTransform)
4722 : {
4723 1240 : std::vector<double> adfX, adfY, adfZ;
4724 1240 : std::vector<int> abSuccess;
4725 :
4726 620 : GDALDatasetH hSrcDS = pahSrcDS[0];
4727 :
4728 : // Sample points on a N x N grid in the source raster
4729 620 : constexpr int N = 10;
4730 620 : const int nSrcXSize = GDALGetRasterXSize(hSrcDS);
4731 620 : const int nSrcYSize = GDALGetRasterYSize(hSrcDS);
4732 7440 : for (int j = 0; j <= N; ++j)
4733 : {
4734 81840 : for (int i = 0; i <= N; ++i)
4735 : {
4736 75020 : adfX.push_back(static_cast<double>(i) / N *
4737 : nSrcXSize);
4738 75020 : adfY.push_back(static_cast<double>(j) / N *
4739 : nSrcYSize);
4740 75020 : adfZ.push_back(0);
4741 75020 : abSuccess.push_back(0);
4742 : }
4743 : }
4744 :
4745 : {
4746 1240 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
4747 :
4748 : // Transform from source raster coordinates to target raster
4749 : // coordinates
4750 620 : psInfo->pfnTransform(hUniqueTransformArg.get(), FALSE,
4751 620 : static_cast<int>(adfX.size()),
4752 620 : &adfX[0], &adfY[0], &adfZ[0],
4753 620 : &abSuccess[0]);
4754 :
4755 620 : const int nDstXSize = nPixels;
4756 620 : const int nDstYSize = nLines;
4757 :
4758 : // Clamp target raster coordinates
4759 75640 : for (size_t i = 0; i < adfX.size(); ++i)
4760 : {
4761 75020 : if (adfX[i] < 0)
4762 1100 : adfX[i] = 0;
4763 75020 : if (adfX[i] > nDstXSize)
4764 2981 : adfX[i] = nDstXSize;
4765 75020 : if (adfY[i] < 0)
4766 1414 : adfY[i] = 0;
4767 75020 : if (adfY[i] > nDstYSize)
4768 3590 : adfY[i] = nDstYSize;
4769 : }
4770 :
4771 : // Start recording if different coordinate operations are
4772 : // going to be used
4773 620 : OGRProjCTDifferentOperationsStart(
4774 620 : psRTI->poReverseTransform);
4775 :
4776 : // Transform back to source raster coordinates.
4777 620 : psInfo->pfnTransform(hUniqueTransformArg.get(), TRUE,
4778 620 : static_cast<int>(adfX.size()),
4779 620 : &adfX[0], &adfY[0], &adfZ[0],
4780 620 : &abSuccess[0]);
4781 : }
4782 :
4783 620 : if (OGRProjCTDifferentOperationsUsed(
4784 620 : psRTI->poReverseTransform))
4785 : {
4786 0 : const char *pszTransformOption =
4787 0 : psOptions->bInvokedFromGdalAlgorithm
4788 0 : ? "--transform-option"
4789 : : "-to";
4790 0 : const char *pszCoordinateOperation =
4791 0 : psOptions->bInvokedFromGdalAlgorithm
4792 0 : ? ""
4793 : : ", or specify a particular coordinate "
4794 : "operation with -ct";
4795 :
4796 0 : CPLError(
4797 : CE_Warning, CPLE_AppDefined,
4798 : "Several coordinate operations are going to be "
4799 : "used. Artifacts may appear. You may consider "
4800 : "using the %s ALLOW_BALLPARK=NO and/or "
4801 : "%s ONLY_BEST=YES transform options%s",
4802 : pszTransformOption, pszTransformOption,
4803 : pszCoordinateOperation);
4804 : }
4805 :
4806 : // Stop recording
4807 620 : OGRProjCTDifferentOperationsStop(psRTI->poReverseTransform);
4808 : }
4809 : }
4810 : }
4811 : }
4812 :
4813 : /* -------------------------------------------------------------------- */
4814 : /* Try to set color interpretation of source bands to target */
4815 : /* dataset. */
4816 : /* FIXME? We should likely do that for other drivers than VRT & */
4817 : /* GTiff but it might create spurious .aux.xml files (at least */
4818 : /* with HFA, and netCDF) */
4819 : /* -------------------------------------------------------------------- */
4820 1054 : if (bVRT || bSetColorInterpretation)
4821 : {
4822 637 : int nBandsToCopy = static_cast<int>(apeColorInterpretations.size());
4823 637 : if (psOptions->bEnableSrcAlpha)
4824 30 : nBandsToCopy--;
4825 1457 : for (int iBand = 0; iBand < nBandsToCopy; iBand++)
4826 : {
4827 820 : GDALSetRasterColorInterpretation(
4828 : GDALGetRasterBand(hDstDS, iBand + 1),
4829 820 : apeColorInterpretations[iBand]);
4830 : }
4831 : }
4832 :
4833 : /* -------------------------------------------------------------------- */
4834 : /* Try to set color interpretation of output file alpha band. */
4835 : /* -------------------------------------------------------------------- */
4836 1054 : if (psOptions->bEnableDstAlpha)
4837 : {
4838 99 : GDALSetRasterColorInterpretation(
4839 : GDALGetRasterBand(hDstDS, nDstBandCount), GCI_AlphaBand);
4840 : }
4841 :
4842 : /* -------------------------------------------------------------------- */
4843 : /* Copy the raster attribute table, if required. */
4844 : /* -------------------------------------------------------------------- */
4845 1054 : if (hRAT != nullptr)
4846 : {
4847 0 : GDALSetDefaultRAT(GDALGetRasterBand(hDstDS, 1), hRAT);
4848 : }
4849 :
4850 : /* -------------------------------------------------------------------- */
4851 : /* Copy the color table, if required. */
4852 : /* -------------------------------------------------------------------- */
4853 1054 : if (poCT)
4854 : {
4855 6 : GDALSetRasterColorTable(GDALGetRasterBand(hDstDS, 1),
4856 : GDALColorTable::ToHandle(poCT.get()));
4857 : }
4858 :
4859 : /* -------------------------------------------------------------------- */
4860 : /* Copy scale/offset if found on source */
4861 : /* -------------------------------------------------------------------- */
4862 1054 : if (nSrcCount == 1)
4863 : {
4864 1030 : GDALDataset *poSrcDS = GDALDataset::FromHandle(pahSrcDS[0]);
4865 1030 : GDALDataset *poDstDS = GDALDataset::FromHandle(hDstDS);
4866 :
4867 1030 : int nBandsToCopy = nDstBandCount;
4868 1030 : if (psOptions->bEnableDstAlpha)
4869 96 : nBandsToCopy--;
4870 1030 : nBandsToCopy = std::min(nBandsToCopy, poSrcDS->GetRasterCount());
4871 :
4872 2429 : for (int i = 0; i < nBandsToCopy; i++)
4873 : {
4874 1439 : auto poSrcBand = poSrcDS->GetRasterBand(
4875 1399 : psOptions->anSrcBands.empty() ? i + 1
4876 40 : : psOptions->anSrcBands[i]);
4877 1439 : auto poDstBand = poDstDS->GetRasterBand(
4878 1399 : psOptions->anDstBands.empty() ? i + 1
4879 40 : : psOptions->anDstBands[i]);
4880 1399 : if (poSrcBand && poDstBand)
4881 : {
4882 1397 : int bHasScale = FALSE;
4883 1397 : const double dfScale = poSrcBand->GetScale(&bHasScale);
4884 1397 : if (bHasScale)
4885 42 : poDstBand->SetScale(dfScale);
4886 :
4887 1397 : int bHasOffset = FALSE;
4888 1397 : const double dfOffset = poSrcBand->GetOffset(&bHasOffset);
4889 1397 : if (bHasOffset)
4890 42 : poDstBand->SetOffset(dfOffset);
4891 : }
4892 : }
4893 : }
4894 :
4895 1054 : return hDstDS;
4896 : }
4897 :
4898 : /************************************************************************/
4899 : /* GeoTransform_Transformer() */
4900 : /* */
4901 : /* Convert points from georef coordinates to pixel/line based */
4902 : /* on a geotransform. */
4903 : /************************************************************************/
4904 : namespace
4905 : {
4906 : class CutlineTransformer final : public OGRCoordinateTransformation
4907 : {
4908 : CPL_DISALLOW_COPY_ASSIGN(CutlineTransformer)
4909 :
4910 : public:
4911 : void *hSrcImageTransformer = nullptr;
4912 :
4913 46 : explicit CutlineTransformer(void *hTransformArg)
4914 46 : : hSrcImageTransformer(hTransformArg)
4915 : {
4916 46 : }
4917 :
4918 0 : const OGRSpatialReference *GetSourceCS() const override
4919 : {
4920 0 : return nullptr;
4921 : }
4922 :
4923 185 : const OGRSpatialReference *GetTargetCS() const override
4924 : {
4925 185 : return nullptr;
4926 : }
4927 :
4928 : ~CutlineTransformer() override;
4929 :
4930 72 : virtual int Transform(size_t nCount, double *x, double *y, double *z,
4931 : double * /* t */, int *pabSuccess) override
4932 : {
4933 72 : CPLAssert(nCount <=
4934 : static_cast<size_t>(std::numeric_limits<int>::max()));
4935 72 : return GDALGenImgProjTransform(hSrcImageTransformer, TRUE,
4936 : static_cast<int>(nCount), x, y, z,
4937 72 : pabSuccess);
4938 : }
4939 :
4940 0 : OGRCoordinateTransformation *Clone() const override
4941 : {
4942 : return new CutlineTransformer(
4943 0 : GDALCloneTransformer(hSrcImageTransformer));
4944 : }
4945 :
4946 0 : OGRCoordinateTransformation *GetInverse() const override
4947 : {
4948 0 : return nullptr;
4949 : }
4950 : };
4951 :
4952 46 : CutlineTransformer::~CutlineTransformer()
4953 : {
4954 46 : GDALDestroyTransformer(hSrcImageTransformer);
4955 46 : }
4956 : } // namespace
4957 :
4958 312 : static double GetMaximumSegmentLength(const OGRGeometry *poGeom)
4959 : {
4960 312 : switch (wkbFlatten(poGeom->getGeometryType()))
4961 : {
4962 120 : case wkbLineString:
4963 : {
4964 120 : const OGRLineString *poLS = poGeom->toLineString();
4965 120 : double dfMaxSquaredLength = 0.0;
4966 16620 : for (int i = 0; i < poLS->getNumPoints() - 1; i++)
4967 : {
4968 16500 : double dfDeltaX = poLS->getX(i + 1) - poLS->getX(i);
4969 16500 : double dfDeltaY = poLS->getY(i + 1) - poLS->getY(i);
4970 16500 : double dfSquaredLength =
4971 16500 : dfDeltaX * dfDeltaX + dfDeltaY * dfDeltaY;
4972 16500 : dfMaxSquaredLength =
4973 16500 : std::max(dfMaxSquaredLength, dfSquaredLength);
4974 : }
4975 120 : return sqrt(dfMaxSquaredLength);
4976 : }
4977 :
4978 118 : case wkbPolygon:
4979 : {
4980 118 : const OGRPolygon *poPoly = poGeom->toPolygon();
4981 118 : double dfMaxLength = 0;
4982 238 : for (const auto *poRing : *poPoly)
4983 : {
4984 120 : dfMaxLength =
4985 120 : std::max(dfMaxLength, GetMaximumSegmentLength(poRing));
4986 : }
4987 118 : return dfMaxLength;
4988 : }
4989 :
4990 74 : case wkbMultiPolygon:
4991 : {
4992 74 : const OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
4993 74 : double dfMaxLength = 0.0;
4994 152 : for (const auto *poPoly : *poMP)
4995 : {
4996 78 : dfMaxLength =
4997 78 : std::max(dfMaxLength, GetMaximumSegmentLength(poPoly));
4998 : }
4999 74 : return dfMaxLength;
5000 : }
5001 :
5002 0 : default:
5003 0 : CPLAssert(false);
5004 : return 0.0;
5005 : }
5006 : }
5007 :
5008 : /************************************************************************/
5009 : /* RemoveZeroWidthSlivers() */
5010 : /* */
5011 : /* Such slivers can cause issues after reprojection. */
5012 : /************************************************************************/
5013 :
5014 130 : static void RemoveZeroWidthSlivers(OGRGeometry *poGeom)
5015 : {
5016 130 : const OGRwkbGeometryType eType = wkbFlatten(poGeom->getGeometryType());
5017 130 : if (eType == wkbMultiPolygon)
5018 : {
5019 32 : auto poMP = poGeom->toMultiPolygon();
5020 32 : int nNumGeometries = poMP->getNumGeometries();
5021 66 : for (int i = 0; i < nNumGeometries; /* incremented in loop */)
5022 : {
5023 34 : auto poPoly = poMP->getGeometryRef(i);
5024 34 : RemoveZeroWidthSlivers(poPoly);
5025 34 : if (poPoly->IsEmpty())
5026 : {
5027 1 : CPLDebug("WARP",
5028 : "RemoveZeroWidthSlivers: removing empty polygon");
5029 1 : poMP->removeGeometry(i, /* bDelete = */ true);
5030 1 : --nNumGeometries;
5031 : }
5032 : else
5033 : {
5034 33 : ++i;
5035 : }
5036 : }
5037 : }
5038 98 : else if (eType == wkbPolygon)
5039 : {
5040 48 : auto poPoly = poGeom->toPolygon();
5041 48 : if (auto poExteriorRing = poPoly->getExteriorRing())
5042 : {
5043 48 : RemoveZeroWidthSlivers(poExteriorRing);
5044 48 : if (poExteriorRing->getNumPoints() < 4)
5045 : {
5046 1 : poPoly->empty();
5047 1 : return;
5048 : }
5049 : }
5050 47 : int nNumInteriorRings = poPoly->getNumInteriorRings();
5051 49 : for (int i = 0; i < nNumInteriorRings; /* incremented in loop */)
5052 : {
5053 2 : auto poRing = poPoly->getInteriorRing(i);
5054 2 : RemoveZeroWidthSlivers(poRing);
5055 2 : if (poRing->getNumPoints() < 4)
5056 : {
5057 1 : CPLDebug(
5058 : "WARP",
5059 : "RemoveZeroWidthSlivers: removing empty interior ring");
5060 1 : constexpr int OFFSET_EXTERIOR_RING = 1;
5061 1 : poPoly->removeRing(i + OFFSET_EXTERIOR_RING,
5062 : /* bDelete = */ true);
5063 1 : --nNumInteriorRings;
5064 : }
5065 : else
5066 : {
5067 1 : ++i;
5068 : }
5069 : }
5070 : }
5071 50 : else if (eType == wkbLineString)
5072 : {
5073 50 : OGRLineString *poLS = poGeom->toLineString();
5074 50 : int numPoints = poLS->getNumPoints();
5075 691 : for (int i = 1; i < numPoints - 1;)
5076 : {
5077 641 : const double x1 = poLS->getX(i - 1);
5078 641 : const double y1 = poLS->getY(i - 1);
5079 641 : const double x2 = poLS->getX(i);
5080 641 : const double y2 = poLS->getY(i);
5081 641 : const double x3 = poLS->getX(i + 1);
5082 641 : const double y3 = poLS->getY(i + 1);
5083 641 : const double dx1 = x2 - x1;
5084 641 : const double dy1 = y2 - y1;
5085 641 : const double dx2 = x3 - x2;
5086 641 : const double dy2 = y3 - y2;
5087 641 : const double scalar_product = dx1 * dx2 + dy1 * dy2;
5088 641 : const double square_scalar_product =
5089 : scalar_product * scalar_product;
5090 641 : const double square_norm1 = dx1 * dx1 + dy1 * dy1;
5091 641 : const double square_norm2 = dx2 * dx2 + dy2 * dy2;
5092 641 : const double square_norm1_mult_norm2 = square_norm1 * square_norm2;
5093 641 : if (scalar_product < 0 &&
5094 75 : fabs(square_scalar_product - square_norm1_mult_norm2) <=
5095 75 : 1e-15 * square_norm1_mult_norm2)
5096 : {
5097 5 : CPLDebug("WARP",
5098 : "RemoveZeroWidthSlivers: removing point %.10g %.10g",
5099 : x2, y2);
5100 5 : poLS->removePoint(i);
5101 5 : numPoints--;
5102 : }
5103 : else
5104 : {
5105 636 : ++i;
5106 : }
5107 : }
5108 : }
5109 : }
5110 :
5111 : /************************************************************************/
5112 : /* TransformCutlineToSource() */
5113 : /* */
5114 : /* Transform cutline from its SRS to source pixel/line coordinates.*/
5115 : /************************************************************************/
5116 46 : static CPLErr TransformCutlineToSource(GDALDataset *poSrcDS,
5117 : OGRGeometry *poCutline,
5118 : char ***ppapszWarpOptions,
5119 : CSLConstList papszTO_In)
5120 :
5121 : {
5122 46 : RemoveZeroWidthSlivers(poCutline);
5123 :
5124 92 : auto poMultiPolygon = std::unique_ptr<OGRGeometry>(poCutline->clone());
5125 :
5126 : /* -------------------------------------------------------------------- */
5127 : /* Checkout that if there's a cutline SRS, there's also a raster */
5128 : /* one. */
5129 : /* -------------------------------------------------------------------- */
5130 46 : std::unique_ptr<OGRSpatialReference> poRasterSRS;
5131 : const CPLString osProjection =
5132 92 : GetSrcDSProjection(GDALDataset::ToHandle(poSrcDS), papszTO_In);
5133 46 : if (!osProjection.empty())
5134 : {
5135 42 : poRasterSRS = std::make_unique<OGRSpatialReference>();
5136 42 : poRasterSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5137 42 : if (poRasterSRS->SetFromUserInput(osProjection) != OGRERR_NONE)
5138 : {
5139 0 : poRasterSRS.reset();
5140 : }
5141 : }
5142 :
5143 46 : std::unique_ptr<OGRSpatialReference> poDstSRS;
5144 46 : const char *pszThisTargetSRS = CSLFetchNameValue(papszTO_In, "DST_SRS");
5145 46 : if (pszThisTargetSRS)
5146 : {
5147 8 : poDstSRS = std::make_unique<OGRSpatialReference>();
5148 8 : poDstSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
5149 8 : if (poDstSRS->SetFromUserInput(pszThisTargetSRS) != OGRERR_NONE)
5150 : {
5151 0 : return CE_Failure;
5152 : }
5153 : }
5154 38 : else if (poRasterSRS)
5155 : {
5156 34 : poDstSRS.reset(poRasterSRS->Clone());
5157 : }
5158 :
5159 : /* -------------------------------------------------------------------- */
5160 : /* Extract the cutline SRS. */
5161 : /* -------------------------------------------------------------------- */
5162 : const OGRSpatialReference *poCutlineSRS =
5163 46 : poMultiPolygon->getSpatialReference();
5164 :
5165 : /* -------------------------------------------------------------------- */
5166 : /* Detect if there's no transform at all involved, in which case */
5167 : /* we can avoid densification. */
5168 : /* -------------------------------------------------------------------- */
5169 46 : bool bMayNeedDensify = true;
5170 88 : if (poRasterSRS && poCutlineSRS && poRasterSRS->IsSame(poCutlineSRS) &&
5171 32 : poSrcDS->GetGCPCount() == 0 && !poSrcDS->GetMetadata(GDAL_MDD_RPC) &&
5172 28 : !poSrcDS->GetMetadata(GDAL_MDD_GEOLOCATION) &&
5173 116 : !CSLFetchNameValue(papszTO_In, "GEOLOC_ARRAY") &&
5174 28 : !CSLFetchNameValue(papszTO_In, "SRC_GEOLOC_ARRAY"))
5175 : {
5176 56 : CPLStringList aosTOTmp(papszTO_In);
5177 28 : aosTOTmp.SetNameValue("SRC_SRS", nullptr);
5178 28 : aosTOTmp.SetNameValue("DST_SRS", nullptr);
5179 28 : if (aosTOTmp.size() == 0)
5180 : {
5181 22 : bMayNeedDensify = false;
5182 : }
5183 : }
5184 :
5185 : /* -------------------------------------------------------------------- */
5186 : /* Compare source raster SRS and cutline SRS */
5187 : /* -------------------------------------------------------------------- */
5188 46 : if (poRasterSRS && poCutlineSRS)
5189 : {
5190 : /* OK, we will reproject */
5191 : }
5192 8 : else if (poRasterSRS && !poCutlineSRS)
5193 : {
5194 4 : CPLError(
5195 : CE_Warning, CPLE_AppDefined,
5196 : "the source raster dataset has a SRS, but the cutline features\n"
5197 : "not. We assume that the cutline coordinates are expressed in the "
5198 : "destination SRS.\n"
5199 : "If not, cutline results may be incorrect.");
5200 : }
5201 4 : else if (!poRasterSRS && poCutlineSRS)
5202 : {
5203 0 : CPLError(CE_Warning, CPLE_AppDefined,
5204 : "the input vector layer has a SRS, but the source raster "
5205 : "dataset does not.\n"
5206 : "Cutline results may be incorrect.");
5207 : }
5208 :
5209 : auto poCTCutlineToSrc = CreateCTCutlineToSrc(
5210 92 : poRasterSRS.get(), poDstSRS.get(), poCutlineSRS, papszTO_In);
5211 :
5212 92 : CPLStringList aosTO(papszTO_In);
5213 :
5214 46 : if (pszThisTargetSRS && !osProjection.empty())
5215 : {
5216 : // Avoid any reprojection when using the GenImgProjTransformer
5217 8 : aosTO.SetNameValue("DST_SRS", osProjection.c_str());
5218 : }
5219 46 : aosTO.SetNameValue("SRC_COORDINATE_EPOCH", nullptr);
5220 46 : aosTO.SetNameValue("DST_COORDINATE_EPOCH", nullptr);
5221 46 : aosTO.SetNameValue("COORDINATE_OPERATION", nullptr);
5222 :
5223 : /* -------------------------------------------------------------------- */
5224 : /* It may be unwise to let the mask geometry be re-wrapped by */
5225 : /* the CENTER_LONG machinery as this can easily screw up world */
5226 : /* spanning masks and invert the mask topology. */
5227 : /* -------------------------------------------------------------------- */
5228 46 : aosTO.SetNameValue("INSERT_CENTER_LONG", "FALSE");
5229 :
5230 : /* -------------------------------------------------------------------- */
5231 : /* Transform the geometry to pixel/line coordinates. */
5232 : /* -------------------------------------------------------------------- */
5233 : /* The cutline transformer will *invert* the hSrcImageTransformer */
5234 : /* so it will convert from the source SRS to the source pixel/line */
5235 : /* coordinates */
5236 : CutlineTransformer oTransformer(GDALCreateGenImgProjTransformer2(
5237 92 : GDALDataset::ToHandle(poSrcDS), nullptr, aosTO.List()));
5238 :
5239 46 : if (oTransformer.hSrcImageTransformer == nullptr)
5240 : {
5241 0 : return CE_Failure;
5242 : }
5243 :
5244 : // Some transforms like RPC can transform a valid geometry into an invalid
5245 : // one if the node density of the input geometry isn't sufficient before
5246 : // reprojection. So after an initial reprojection, we check that the
5247 : // maximum length of a segment is no longer than 1 pixel, and if not,
5248 : // we densify the input geometry before doing a new reprojection
5249 : const double dfMaxLengthInSpatUnits =
5250 46 : GetMaximumSegmentLength(poMultiPolygon.get());
5251 46 : OGRErr eErr = OGRERR_NONE;
5252 46 : if (poCTCutlineToSrc)
5253 : {
5254 12 : poMultiPolygon.reset(OGRGeometryFactory::transformWithOptions(
5255 6 : poMultiPolygon.get(), poCTCutlineToSrc.get(), nullptr));
5256 6 : if (!poMultiPolygon)
5257 : {
5258 0 : eErr = OGRERR_FAILURE;
5259 0 : poMultiPolygon.reset(poCutline->clone());
5260 0 : poMultiPolygon->transform(poCTCutlineToSrc.get());
5261 : }
5262 : }
5263 46 : if (poMultiPolygon->transform(&oTransformer) != OGRERR_NONE)
5264 : {
5265 0 : CPLError(CE_Failure, CPLE_AppDefined,
5266 : "poMultiPolygon->transform(&oTransformer) failed at line %d",
5267 : __LINE__);
5268 0 : eErr = OGRERR_FAILURE;
5269 : }
5270 : const double dfInitialMaxLengthInPixels =
5271 46 : GetMaximumSegmentLength(poMultiPolygon.get());
5272 :
5273 46 : CPLPushErrorHandler(CPLQuietErrorHandler);
5274 : const bool bWasValidInitially =
5275 46 : ValidateCutline(poMultiPolygon.get(), false);
5276 46 : CPLPopErrorHandler();
5277 46 : if (!bWasValidInitially)
5278 : {
5279 3 : CPLDebug("WARP", "Cutline is not valid after initial reprojection");
5280 3 : char *pszWKT = nullptr;
5281 3 : poMultiPolygon->exportToWkt(&pszWKT);
5282 3 : CPLDebug("GDALWARP", "WKT = \"%s\"", pszWKT ? pszWKT : "(null)");
5283 3 : CPLFree(pszWKT);
5284 : }
5285 :
5286 46 : bool bDensify = false;
5287 46 : if (bMayNeedDensify && eErr == OGRERR_NONE &&
5288 : dfInitialMaxLengthInPixels > 1.0)
5289 : {
5290 : const char *pszDensifyCutline =
5291 22 : CPLGetConfigOption("GDALWARP_DENSIFY_CUTLINE", "YES");
5292 22 : if (EQUAL(pszDensifyCutline, "ONLY_IF_INVALID"))
5293 : {
5294 1 : bDensify = (OGRGeometryFactory::haveGEOS() && !bWasValidInitially);
5295 : }
5296 21 : else if (CSLFetchNameValue(*ppapszWarpOptions, "CUTLINE_BLEND_DIST") !=
5297 21 : nullptr &&
5298 0 : CPLGetConfigOption("GDALWARP_DENSIFY_CUTLINE", nullptr) ==
5299 : nullptr)
5300 : {
5301 : // TODO: we should only emit this message if a
5302 : // transform/reprojection will be actually done
5303 0 : CPLDebug("WARP",
5304 : "Densification of cutline could perhaps be useful but as "
5305 : "CUTLINE_BLEND_DIST is used, this could be very slow. So "
5306 : "disabled "
5307 : "unless GDALWARP_DENSIFY_CUTLINE=YES is explicitly "
5308 : "specified as configuration option");
5309 : }
5310 : else
5311 : {
5312 21 : bDensify = CPLTestBool(pszDensifyCutline);
5313 : }
5314 : }
5315 46 : if (bDensify)
5316 : {
5317 21 : CPLDebug("WARP",
5318 : "Cutline maximum segment size was %.0f pixel after "
5319 : "reprojection to source coordinates.",
5320 : dfInitialMaxLengthInPixels);
5321 :
5322 : // Densify and reproject with the aim of having a 1 pixel density
5323 21 : double dfSegmentSize =
5324 : dfMaxLengthInSpatUnits / dfInitialMaxLengthInPixels;
5325 21 : const int MAX_ITERATIONS = 10;
5326 22 : for (int i = 0; i < MAX_ITERATIONS; i++)
5327 : {
5328 22 : poMultiPolygon.reset(poCutline->clone());
5329 22 : poMultiPolygon->segmentize(dfSegmentSize);
5330 22 : if (i == MAX_ITERATIONS - 1)
5331 : {
5332 0 : char *pszWKT = nullptr;
5333 0 : poMultiPolygon->exportToWkt(&pszWKT);
5334 0 : CPLDebug("WARP",
5335 : "WKT of polygon after densification with segment size "
5336 : "= %f: %s",
5337 : dfSegmentSize, pszWKT);
5338 0 : CPLFree(pszWKT);
5339 : }
5340 22 : eErr = OGRERR_NONE;
5341 22 : if (poCTCutlineToSrc)
5342 : {
5343 12 : poMultiPolygon.reset(OGRGeometryFactory::transformWithOptions(
5344 6 : poMultiPolygon.get(), poCTCutlineToSrc.get(), nullptr));
5345 6 : if (!poMultiPolygon)
5346 : {
5347 0 : eErr = OGRERR_FAILURE;
5348 0 : break;
5349 : }
5350 : }
5351 22 : if (poMultiPolygon->transform(&oTransformer) != OGRERR_NONE)
5352 0 : eErr = OGRERR_FAILURE;
5353 22 : if (eErr == OGRERR_NONE)
5354 : {
5355 : const double dfMaxLengthInPixels =
5356 22 : GetMaximumSegmentLength(poMultiPolygon.get());
5357 22 : if (bWasValidInitially)
5358 : {
5359 : // In some cases, the densification itself results in a
5360 : // reprojected invalid polygon due to the non-linearity of
5361 : // RPC DEM transformation, so in those cases, try a less
5362 : // dense cutline
5363 20 : CPLPushErrorHandler(CPLQuietErrorHandler);
5364 : const bool bIsValid =
5365 20 : ValidateCutline(poMultiPolygon.get(), false);
5366 20 : CPLPopErrorHandler();
5367 20 : if (!bIsValid)
5368 : {
5369 1 : if (i == MAX_ITERATIONS - 1)
5370 : {
5371 0 : char *pszWKT = nullptr;
5372 0 : poMultiPolygon->exportToWkt(&pszWKT);
5373 0 : CPLDebug("WARP",
5374 : "After densification, cutline maximum "
5375 : "segment size is now %.0f pixel, "
5376 : "but cutline is invalid. %s",
5377 : dfMaxLengthInPixels, pszWKT);
5378 0 : CPLFree(pszWKT);
5379 0 : break;
5380 : }
5381 1 : CPLDebug("WARP",
5382 : "After densification, cutline maximum segment "
5383 : "size is now %.0f pixel, "
5384 : "but cutline is invalid. So trying a less "
5385 : "dense cutline.",
5386 : dfMaxLengthInPixels);
5387 1 : dfSegmentSize *= 2;
5388 1 : continue;
5389 : }
5390 : }
5391 21 : CPLDebug("WARP",
5392 : "After densification, cutline maximum segment size is "
5393 : "now %.0f pixel.",
5394 : dfMaxLengthInPixels);
5395 : }
5396 21 : break;
5397 : }
5398 : }
5399 :
5400 46 : if (eErr == OGRERR_FAILURE)
5401 : {
5402 0 : if (CPLTestBool(
5403 : CPLGetConfigOption("GDALWARP_IGNORE_BAD_CUTLINE", "NO")))
5404 0 : CPLError(CE_Warning, CPLE_AppDefined,
5405 : "Cutline transformation failed");
5406 : else
5407 : {
5408 0 : CPLError(CE_Failure, CPLE_AppDefined,
5409 : "Cutline transformation failed");
5410 0 : return CE_Failure;
5411 : }
5412 : }
5413 46 : else if (!ValidateCutline(poMultiPolygon.get(), true))
5414 : {
5415 1 : return CE_Failure;
5416 : }
5417 :
5418 : // Optimization: if the cutline contains the footprint of the source
5419 : // dataset, no need to use the cutline.
5420 45 : if (OGRGeometryFactory::haveGEOS()
5421 : #ifdef DEBUG
5422 : // Env var just for debugging purposes
5423 45 : && !CPLTestBool(CPLGetConfigOption(
5424 : "GDALWARP_SKIP_CUTLINE_CONTAINMENT_TEST", "NO"))
5425 : #endif
5426 : )
5427 : {
5428 44 : const double dfCutlineBlendDist = CPLAtof(CSLFetchNameValueDef(
5429 : *ppapszWarpOptions, "CUTLINE_BLEND_DIST", "0"));
5430 44 : auto poRing = std::make_unique<OGRLinearRing>();
5431 44 : poRing->addPoint(-dfCutlineBlendDist, -dfCutlineBlendDist);
5432 44 : poRing->addPoint(-dfCutlineBlendDist,
5433 44 : dfCutlineBlendDist + poSrcDS->GetRasterYSize());
5434 88 : poRing->addPoint(dfCutlineBlendDist + poSrcDS->GetRasterXSize(),
5435 44 : dfCutlineBlendDist + poSrcDS->GetRasterYSize());
5436 44 : poRing->addPoint(dfCutlineBlendDist + poSrcDS->GetRasterXSize(),
5437 : -dfCutlineBlendDist);
5438 44 : poRing->addPoint(-dfCutlineBlendDist, -dfCutlineBlendDist);
5439 44 : OGRPolygon oSrcDSFootprint;
5440 44 : oSrcDSFootprint.addRing(std::move(poRing));
5441 44 : OGREnvelope sSrcDSEnvelope;
5442 44 : oSrcDSFootprint.getEnvelope(&sSrcDSEnvelope);
5443 44 : OGREnvelope sCutlineEnvelope;
5444 44 : poMultiPolygon->getEnvelope(&sCutlineEnvelope);
5445 55 : if (sCutlineEnvelope.Contains(sSrcDSEnvelope) &&
5446 11 : poMultiPolygon->Contains(&oSrcDSFootprint))
5447 : {
5448 3 : CPLDebug("WARP", "Source dataset fully contained within cutline.");
5449 3 : return CE_None;
5450 : }
5451 : }
5452 :
5453 : /* -------------------------------------------------------------------- */
5454 : /* Convert aggregate geometry into WKT. */
5455 : /* -------------------------------------------------------------------- */
5456 42 : char *pszWKT = nullptr;
5457 42 : poMultiPolygon->exportToWkt(&pszWKT);
5458 : // fprintf(stderr, "WKT = \"%s\"\n", pszWKT ? pszWKT : "(null)");
5459 :
5460 42 : *ppapszWarpOptions = CSLSetNameValue(*ppapszWarpOptions, "CUTLINE", pszWKT);
5461 42 : CPLFree(pszWKT);
5462 42 : return CE_None;
5463 : }
5464 :
5465 47 : static void RemoveConflictingMetadata(GDALMajorObjectH hObj,
5466 : CSLConstList papszSrcMetadata,
5467 : const char *pszValueConflict)
5468 : {
5469 47 : if (hObj == nullptr)
5470 0 : return;
5471 :
5472 38 : for (const auto &[pszKey, pszValue] :
5473 85 : cpl::IterateNameValue(papszSrcMetadata))
5474 : {
5475 19 : const char *pszValueComp = GDALGetMetadataItem(hObj, pszKey, nullptr);
5476 19 : if (pszValueComp == nullptr || (!EQUAL(pszValue, pszValueComp) &&
5477 2 : !EQUAL(pszValueComp, pszValueConflict)))
5478 : {
5479 3 : if (STARTS_WITH(pszKey, "STATISTICS_"))
5480 1 : GDALSetMetadataItem(hObj, pszKey, nullptr, nullptr);
5481 : else
5482 2 : GDALSetMetadataItem(hObj, pszKey, pszValueConflict, nullptr);
5483 : }
5484 : }
5485 : }
5486 :
5487 : /************************************************************************/
5488 : /* IsValidSRS */
5489 : /************************************************************************/
5490 :
5491 333 : static bool IsValidSRS(const char *pszUserInput)
5492 :
5493 : {
5494 : OGRSpatialReferenceH hSRS;
5495 333 : bool bRes = true;
5496 :
5497 333 : hSRS = OSRNewSpatialReference(nullptr);
5498 333 : if (OSRSetFromUserInput(hSRS, pszUserInput) != OGRERR_NONE)
5499 : {
5500 0 : bRes = false;
5501 : }
5502 :
5503 333 : OSRDestroySpatialReference(hSRS);
5504 :
5505 333 : return bRes;
5506 : }
5507 :
5508 : /************************************************************************/
5509 : /* GDALWarpAppOptionsGetParser() */
5510 : /************************************************************************/
5511 :
5512 : static std::unique_ptr<GDALArgumentParser>
5513 1185 : GDALWarpAppOptionsGetParser(GDALWarpAppOptions *psOptions,
5514 : GDALWarpAppOptionsForBinary *psOptionsForBinary)
5515 : {
5516 : auto argParser = std::make_unique<GDALArgumentParser>(
5517 1185 : "gdalwarp", /* bForBinary=*/psOptionsForBinary != nullptr);
5518 :
5519 1185 : argParser->add_description(_("Image reprojection and warping utility."));
5520 :
5521 1185 : argParser->add_epilog(
5522 1185 : _("For more details, consult https://gdal.org/programs/gdalwarp.html"));
5523 :
5524 : argParser->add_quiet_argument(
5525 1185 : psOptionsForBinary ? &psOptionsForBinary->bQuiet : nullptr);
5526 :
5527 1185 : argParser->add_argument("-overwrite")
5528 1185 : .flag()
5529 : .action(
5530 90 : [psOptionsForBinary](const std::string &)
5531 : {
5532 49 : if (psOptionsForBinary)
5533 41 : psOptionsForBinary->bOverwrite = true;
5534 1185 : })
5535 1185 : .help(_("Overwrite the target dataset if it already exists."));
5536 :
5537 1185 : argParser->add_output_format_argument(psOptions->osFormat);
5538 :
5539 1185 : argParser->add_argument("-co")
5540 2370 : .metavar("<NAME>=<VALUE>")
5541 1185 : .append()
5542 : .action(
5543 305 : [psOptions, psOptionsForBinary](const std::string &s)
5544 : {
5545 149 : psOptions->aosCreateOptions.AddString(s.c_str());
5546 149 : psOptions->bCreateOutput = true;
5547 :
5548 149 : if (psOptionsForBinary)
5549 7 : psOptionsForBinary->aosCreateOptions.AddString(s.c_str());
5550 1185 : })
5551 1185 : .help(_("Creation option(s)."));
5552 :
5553 1185 : argParser->add_argument("-s_srs")
5554 2370 : .metavar("<srs_def>")
5555 : .action(
5556 64 : [psOptions](const std::string &s)
5557 : {
5558 32 : if (!IsValidSRS(s.c_str()))
5559 : {
5560 0 : throw std::invalid_argument("Invalid SRS for -s_srs");
5561 : }
5562 : psOptions->aosTransformerOptions.SetNameValue("SRC_SRS",
5563 32 : s.c_str());
5564 1217 : })
5565 1185 : .help(_("Set source spatial reference."));
5566 :
5567 1185 : argParser->add_argument("-t_srs")
5568 2370 : .metavar("<srs_def>")
5569 : .action(
5570 572 : [psOptions](const std::string &s)
5571 : {
5572 286 : if (!IsValidSRS(s.c_str()))
5573 : {
5574 0 : throw std::invalid_argument("Invalid SRS for -t_srs");
5575 : }
5576 : psOptions->aosTransformerOptions.SetNameValue("DST_SRS",
5577 286 : s.c_str());
5578 1471 : })
5579 1185 : .help(_("Set target spatial reference."));
5580 :
5581 : {
5582 1185 : auto &group = argParser->add_mutually_exclusive_group();
5583 1185 : group.add_argument("-srcalpha")
5584 1185 : .flag()
5585 1185 : .store_into(psOptions->bEnableSrcAlpha)
5586 : .help(_("Force the last band of a source image to be considered as "
5587 1185 : "a source alpha band."));
5588 1185 : group.add_argument("-nosrcalpha")
5589 1185 : .flag()
5590 1185 : .store_into(psOptions->bDisableSrcAlpha)
5591 : .help(_("Prevent the alpha band of a source image to be considered "
5592 1185 : "as such."));
5593 : }
5594 :
5595 1185 : argParser->add_argument("-dstalpha")
5596 1185 : .flag()
5597 1185 : .store_into(psOptions->bEnableDstAlpha)
5598 : .help(_("Create an output alpha band to identify nodata "
5599 1185 : "(unset/transparent) pixels."));
5600 :
5601 : // Parsing of that option is done in a preprocessing stage
5602 1185 : argParser->add_argument("-tr")
5603 2370 : .metavar("<xres> <yres>|square")
5604 1185 : .help(_("Target resolution."));
5605 :
5606 1185 : argParser->add_argument("-ts")
5607 2370 : .metavar("<width> <height>")
5608 1185 : .nargs(2)
5609 1185 : .scan<'i', int>()
5610 1185 : .help(_("Set output file size in pixels and lines."));
5611 :
5612 1185 : argParser->add_argument("-te")
5613 2370 : .metavar("<xmin> <ymin> <xmax> <ymax>")
5614 1185 : .nargs(4)
5615 1185 : .scan<'g', double>()
5616 1185 : .help(_("Set georeferenced extents of output file to be created."));
5617 :
5618 1185 : argParser->add_argument("-te_srs")
5619 2370 : .metavar("<srs_def>")
5620 : .action(
5621 18 : [psOptions](const std::string &s)
5622 : {
5623 6 : if (!IsValidSRS(s.c_str()))
5624 : {
5625 0 : throw std::invalid_argument("Invalid SRS for -te_srs");
5626 : }
5627 6 : psOptions->osTE_SRS = s;
5628 6 : psOptions->bCreateOutput = true;
5629 1191 : })
5630 1185 : .help(_("Set source spatial reference."));
5631 :
5632 1185 : argParser->add_argument("-r")
5633 : .metavar("near|bilinear|cubic|cubicspline|lanczos|average|rms|mode|min|"
5634 2370 : "max|med|q1|q3|sum")
5635 : .action(
5636 1401 : [psOptions](const std::string &s)
5637 : {
5638 701 : GDALGetWarpResampleAlg(s.c_str(), psOptions->eResampleAlg,
5639 : /*bThrow=*/true);
5640 700 : psOptions->bResampleAlgSpecifiedByUser = true;
5641 1185 : })
5642 1185 : .help(_("Resampling method to use."));
5643 :
5644 1185 : argParser->add_output_type_argument(psOptions->eOutputType);
5645 :
5646 : ///////////////////////////////////////////////////////////////////////
5647 1185 : argParser->add_group("Advanced options");
5648 :
5649 32 : const auto CheckSingleMethod = [psOptions]()
5650 : {
5651 : const char *pszMethod =
5652 16 : FetchSrcMethod(psOptions->aosTransformerOptions);
5653 16 : if (pszMethod)
5654 0 : CPLError(CE_Warning, CPLE_IllegalArg,
5655 : "Warning: only one METHOD can be used. Method %s is "
5656 : "already defined.",
5657 : pszMethod);
5658 : const char *pszMAX_GCP_ORDER =
5659 16 : psOptions->aosTransformerOptions.FetchNameValue("MAX_GCP_ORDER");
5660 16 : if (pszMAX_GCP_ORDER)
5661 0 : CPLError(CE_Warning, CPLE_IllegalArg,
5662 : "Warning: only one METHOD can be used. -order %s "
5663 : "option was specified, so it is likely that "
5664 : "GCP_POLYNOMIAL was implied.",
5665 : pszMAX_GCP_ORDER);
5666 1201 : };
5667 :
5668 1185 : argParser->add_argument("-wo")
5669 2370 : .metavar("<NAME>=<VALUE>")
5670 1185 : .append()
5671 231 : .action([psOptions](const std::string &s)
5672 1416 : { psOptions->aosWarpOptions.AddString(s.c_str()); })
5673 1185 : .help(_("Warping option(s)."));
5674 :
5675 1185 : argParser->add_argument("-multi")
5676 1185 : .flag()
5677 1185 : .store_into(psOptions->bMulti)
5678 1185 : .help(_("Multithreaded input/output."));
5679 :
5680 1185 : argParser->add_argument("-s_coord_epoch")
5681 2370 : .metavar("<epoch>")
5682 : .action(
5683 0 : [psOptions](const std::string &s)
5684 : {
5685 : psOptions->aosTransformerOptions.SetNameValue(
5686 0 : "SRC_COORDINATE_EPOCH", s.c_str());
5687 1185 : })
5688 : .help(_("Assign a coordinate epoch, linked with the source SRS when "
5689 1185 : "-s_srs is used."));
5690 :
5691 1185 : argParser->add_argument("-t_coord_epoch")
5692 2370 : .metavar("<epoch>")
5693 : .action(
5694 0 : [psOptions](const std::string &s)
5695 : {
5696 : psOptions->aosTransformerOptions.SetNameValue(
5697 0 : "DST_COORDINATE_EPOCH", s.c_str());
5698 1185 : })
5699 : .help(_("Assign a coordinate epoch, linked with the output SRS when "
5700 1185 : "-t_srs is used."));
5701 :
5702 1185 : argParser->add_argument("-ct")
5703 2370 : .metavar("<string>")
5704 : .action(
5705 4 : [psOptions](const std::string &s)
5706 : {
5707 : psOptions->aosTransformerOptions.SetNameValue(
5708 4 : "COORDINATE_OPERATION", s.c_str());
5709 1185 : })
5710 1185 : .help(_("Set a coordinate transformation."));
5711 :
5712 : {
5713 1185 : auto &group = argParser->add_mutually_exclusive_group();
5714 1185 : group.add_argument("-tps")
5715 1185 : .flag()
5716 : .action(
5717 10 : [psOptions, CheckSingleMethod](const std::string &)
5718 : {
5719 5 : CheckSingleMethod();
5720 : psOptions->aosTransformerOptions.SetNameValue("SRC_METHOD",
5721 5 : "GCP_TPS");
5722 1185 : })
5723 : .help(_("Force use of thin plate spline transformer based on "
5724 1185 : "available GCPs."));
5725 :
5726 1185 : group.add_argument("-rpc")
5727 1185 : .flag()
5728 : .action(
5729 4 : [psOptions, CheckSingleMethod](const std::string &)
5730 : {
5731 2 : CheckSingleMethod();
5732 : psOptions->aosTransformerOptions.SetNameValue("SRC_METHOD",
5733 2 : GDAL_MDD_RPC);
5734 1185 : })
5735 1185 : .help(_("Force use of RPCs."));
5736 :
5737 1185 : group.add_argument("-geoloc")
5738 1185 : .flag()
5739 : .action(
5740 18 : [psOptions, CheckSingleMethod](const std::string &)
5741 : {
5742 9 : CheckSingleMethod();
5743 : psOptions->aosTransformerOptions.SetNameValue(
5744 9 : "SRC_METHOD", "GEOLOC_ARRAY");
5745 1185 : })
5746 1185 : .help(_("Force use of Geolocation Arrays."));
5747 : }
5748 :
5749 1185 : argParser->add_argument("-order")
5750 2370 : .metavar("<1|2|3>")
5751 1185 : .choices("1", "2", "3")
5752 : .action(
5753 0 : [psOptions](const std::string &s)
5754 : {
5755 : const char *pszMethod =
5756 0 : FetchSrcMethod(psOptions->aosTransformerOptions);
5757 0 : if (pszMethod)
5758 0 : CPLError(
5759 : CE_Warning, CPLE_IllegalArg,
5760 : "Warning: only one METHOD can be used. Method %s is "
5761 : "already defined",
5762 : pszMethod);
5763 : psOptions->aosTransformerOptions.SetNameValue("MAX_GCP_ORDER",
5764 0 : s.c_str());
5765 1185 : })
5766 1185 : .help(_("Order of polynomial used for GCP warping."));
5767 :
5768 : // Parsing of that option is done in a preprocessing stage
5769 1185 : argParser->add_argument("-refine_gcps")
5770 2370 : .metavar("<tolerance> [<minimum_gcps>]")
5771 1185 : .help(_("Refines the GCPs by automatically eliminating outliers."));
5772 :
5773 1185 : argParser->add_argument("-to")
5774 2370 : .metavar("<NAME>=<VALUE>")
5775 1185 : .append()
5776 64 : .action([psOptions](const std::string &s)
5777 1249 : { psOptions->aosTransformerOptions.AddString(s.c_str()); })
5778 1185 : .help(_("Transform option(s)."));
5779 :
5780 1185 : argParser->add_argument("-et")
5781 2370 : .metavar("<err_threshold>")
5782 1185 : .store_into(psOptions->dfErrorThreshold)
5783 : .action(
5784 26 : [psOptions](const std::string &)
5785 : {
5786 13 : if (psOptions->dfErrorThreshold < 0)
5787 : {
5788 : throw std::invalid_argument(
5789 0 : "Invalid value for error threshold");
5790 : }
5791 : psOptions->aosWarpOptions.AddString(CPLSPrintf(
5792 13 : "ERROR_THRESHOLD=%.16g", psOptions->dfErrorThreshold));
5793 1198 : })
5794 1185 : .help(_("Error threshold."));
5795 :
5796 1185 : argParser->add_argument("-wm")
5797 2370 : .metavar("<memory_in_mb>")
5798 : .action(
5799 74 : [psOptions](const std::string &s)
5800 : {
5801 38 : bool bUnitSpecified = false;
5802 : GIntBig nBytes;
5803 38 : if (CPLParseMemorySize(s.c_str(), &nBytes, &bUnitSpecified) ==
5804 : CE_None)
5805 : {
5806 36 : if (!bUnitSpecified && nBytes < 10000)
5807 : {
5808 7 : nBytes *= (1024 * 1024);
5809 : }
5810 36 : psOptions->dfWarpMemoryLimit = static_cast<double>(nBytes);
5811 : }
5812 : else
5813 : {
5814 2 : throw std::invalid_argument("Failed to parse value of -wm");
5815 : }
5816 1221 : })
5817 1185 : .help(_("Set max warp memory."));
5818 :
5819 1185 : argParser->add_argument("-srcnodata")
5820 2370 : .metavar("\"<value>[ <value>]...\"")
5821 1185 : .store_into(psOptions->osSrcNodata)
5822 1185 : .help(_("Nodata masking values for input bands."));
5823 :
5824 1185 : argParser->add_argument("-dstnodata")
5825 2370 : .metavar("\"<value>[ <value>]...\"")
5826 1185 : .store_into(psOptions->osDstNodata)
5827 1185 : .help(_("Nodata masking values for output bands."));
5828 :
5829 1185 : argParser->add_argument("-tap")
5830 1185 : .flag()
5831 1185 : .store_into(psOptions->bTargetAlignedPixels)
5832 1185 : .help(_("Force target aligned pixels."));
5833 :
5834 1185 : argParser->add_argument("-wt")
5835 2370 : .metavar("Byte|Int8|[U]Int{16|32|64}|CInt{16|32}|[C]Float{32|64}")
5836 : .action(
5837 0 : [psOptions](const std::string &s)
5838 : {
5839 0 : psOptions->eWorkingType = GDALGetDataTypeByName(s.c_str());
5840 0 : if (psOptions->eWorkingType == GDT_Unknown)
5841 : {
5842 : throw std::invalid_argument(
5843 0 : std::string("Unknown output pixel type: ").append(s));
5844 : }
5845 1185 : })
5846 1185 : .help(_("Working data type."));
5847 :
5848 : // Non-documented alias of -r nearest
5849 1185 : argParser->add_argument("-rn")
5850 1185 : .flag()
5851 1185 : .hidden()
5852 1 : .action([psOptions](const std::string &)
5853 1185 : { psOptions->eResampleAlg = GRA_NearestNeighbour; })
5854 1185 : .help(_("Nearest neighbour resampling."));
5855 :
5856 : // Non-documented alias of -r bilinear
5857 1185 : argParser->add_argument("-rb")
5858 1185 : .flag()
5859 1185 : .hidden()
5860 2 : .action([psOptions](const std::string &)
5861 1185 : { psOptions->eResampleAlg = GRA_Bilinear; })
5862 1185 : .help(_("Bilinear resampling."));
5863 :
5864 : // Non-documented alias of -r cubic
5865 1185 : argParser->add_argument("-rc")
5866 1185 : .flag()
5867 1185 : .hidden()
5868 1 : .action([psOptions](const std::string &)
5869 1185 : { psOptions->eResampleAlg = GRA_Cubic; })
5870 1185 : .help(_("Cubic resampling."));
5871 :
5872 : // Non-documented alias of -r cubicspline
5873 1185 : argParser->add_argument("-rcs")
5874 1185 : .flag()
5875 1185 : .hidden()
5876 1 : .action([psOptions](const std::string &)
5877 1185 : { psOptions->eResampleAlg = GRA_CubicSpline; })
5878 1185 : .help(_("Cubic spline resampling."));
5879 :
5880 : // Non-documented alias of -r lanczos
5881 1185 : argParser->add_argument("-rl")
5882 1185 : .flag()
5883 1185 : .hidden()
5884 0 : .action([psOptions](const std::string &)
5885 1185 : { psOptions->eResampleAlg = GRA_Lanczos; })
5886 1185 : .help(_("Lanczos resampling."));
5887 :
5888 : // Non-documented alias of -r average
5889 1185 : argParser->add_argument("-ra")
5890 1185 : .flag()
5891 1185 : .hidden()
5892 0 : .action([psOptions](const std::string &)
5893 1185 : { psOptions->eResampleAlg = GRA_Average; })
5894 1185 : .help(_("Average resampling."));
5895 :
5896 : // Non-documented alias of -r rms
5897 1185 : argParser->add_argument("-rrms")
5898 1185 : .flag()
5899 1185 : .hidden()
5900 0 : .action([psOptions](const std::string &)
5901 1185 : { psOptions->eResampleAlg = GRA_RMS; })
5902 1185 : .help(_("RMS resampling."));
5903 :
5904 : // Non-documented alias of -r mode
5905 1185 : argParser->add_argument("-rm")
5906 1185 : .flag()
5907 1185 : .hidden()
5908 0 : .action([psOptions](const std::string &)
5909 1185 : { psOptions->eResampleAlg = GRA_Mode; })
5910 1185 : .help(_("Mode resampling."));
5911 :
5912 1185 : argParser->add_argument("-cutline")
5913 2370 : .metavar("<datasource>|<WKT>")
5914 1185 : .store_into(psOptions->osCutlineDSNameOrWKT)
5915 : .help(_("Enable use of a blend cutline from the name of a vector "
5916 1185 : "dataset or a WKT geometry."));
5917 :
5918 1185 : argParser->add_argument("-cutline_srs")
5919 2370 : .metavar("<srs_def>")
5920 : .action(
5921 18 : [psOptions](const std::string &s)
5922 : {
5923 9 : if (!IsValidSRS(s.c_str()))
5924 : {
5925 0 : throw std::invalid_argument("Invalid SRS for -cutline_srs");
5926 : }
5927 9 : psOptions->osCutlineSRS = s;
5928 1194 : })
5929 1185 : .help(_("Sets/overrides cutline SRS."));
5930 :
5931 1185 : argParser->add_argument("-cwhere")
5932 2370 : .metavar("<expression>")
5933 1185 : .store_into(psOptions->osCWHERE)
5934 1185 : .help(_("Restrict desired cutline features based on attribute query."));
5935 :
5936 : {
5937 1185 : auto &group = argParser->add_mutually_exclusive_group();
5938 1185 : group.add_argument("-cl")
5939 2370 : .metavar("<layername>")
5940 1185 : .store_into(psOptions->osCLayer)
5941 1185 : .help(_("Select the named layer from the cutline datasource."));
5942 :
5943 1185 : group.add_argument("-csql")
5944 2370 : .metavar("<query>")
5945 1185 : .store_into(psOptions->osCSQL)
5946 1185 : .help(_("Select cutline features using an SQL query."));
5947 : }
5948 :
5949 1185 : argParser->add_argument("-cblend")
5950 2370 : .metavar("<distance>")
5951 : .action(
5952 0 : [psOptions](const std::string &s)
5953 : {
5954 : psOptions->aosWarpOptions.SetNameValue("CUTLINE_BLEND_DIST",
5955 0 : s.c_str());
5956 1185 : })
5957 : .help(_(
5958 1185 : "Set a blend distance to use to blend over cutlines (in pixels)."));
5959 :
5960 1185 : argParser->add_argument("-crop_to_cutline")
5961 1185 : .flag()
5962 : .action(
5963 18 : [psOptions](const std::string &)
5964 : {
5965 18 : psOptions->bCropToCutline = true;
5966 18 : psOptions->bCreateOutput = true;
5967 1185 : })
5968 : .help(_("Crop the extent of the target dataset to the extent of the "
5969 1185 : "cutline."));
5970 :
5971 1185 : argParser->add_argument("-nomd")
5972 1185 : .flag()
5973 : .action(
5974 0 : [psOptions](const std::string &)
5975 : {
5976 0 : psOptions->bCopyMetadata = false;
5977 0 : psOptions->bCopyBandInfo = false;
5978 1185 : })
5979 1185 : .help(_("Do not copy metadata."));
5980 :
5981 1185 : argParser->add_argument("-cvmd")
5982 2370 : .metavar("<meta_conflict_value>")
5983 1185 : .store_into(psOptions->osMDConflictValue)
5984 : .help(_("Value to set metadata items that conflict between source "
5985 1185 : "datasets."));
5986 :
5987 1185 : argParser->add_argument("-setci")
5988 1185 : .flag()
5989 1185 : .store_into(psOptions->bSetColorInterpretation)
5990 : .help(_("Set the color interpretation of the bands of the target "
5991 1185 : "dataset from the source dataset."));
5992 :
5993 : argParser->add_open_options_argument(
5994 1185 : psOptionsForBinary ? &(psOptionsForBinary->aosOpenOptions) : nullptr);
5995 :
5996 1185 : argParser->add_argument("-doo")
5997 2370 : .metavar("<NAME>=<VALUE>")
5998 1185 : .append()
5999 : .action(
6000 0 : [psOptionsForBinary](const std::string &s)
6001 : {
6002 0 : if (psOptionsForBinary)
6003 0 : psOptionsForBinary->aosDestOpenOptions.AddString(s.c_str());
6004 1185 : })
6005 1185 : .help(_("Open option(s) for output dataset."));
6006 :
6007 1185 : argParser->add_argument("-ovr")
6008 2370 : .metavar("<level>|AUTO|AUTO-<n>|NONE")
6009 : .action(
6010 24 : [psOptions](const std::string &s)
6011 : {
6012 12 : const char *pszOvLevel = s.c_str();
6013 12 : if (EQUAL(pszOvLevel, "AUTO"))
6014 1 : psOptions->nOvLevel = OVR_LEVEL_AUTO;
6015 11 : else if (STARTS_WITH_CI(pszOvLevel, "AUTO-"))
6016 1 : psOptions->nOvLevel =
6017 1 : OVR_LEVEL_AUTO - atoi(pszOvLevel + strlen("AUTO-"));
6018 10 : else if (EQUAL(pszOvLevel, "NONE"))
6019 5 : psOptions->nOvLevel = OVR_LEVEL_NONE;
6020 5 : else if (CPLGetValueType(pszOvLevel) == CPL_VALUE_INTEGER)
6021 5 : psOptions->nOvLevel = atoi(pszOvLevel);
6022 : else
6023 : {
6024 : throw std::invalid_argument(CPLSPrintf(
6025 0 : "Invalid value '%s' for -ov option", pszOvLevel));
6026 : }
6027 1197 : })
6028 1185 : .help(_("Specify which overview level of source files must be used."));
6029 :
6030 : {
6031 1185 : auto &group = argParser->add_mutually_exclusive_group();
6032 1185 : group.add_argument("-vshift")
6033 1185 : .flag()
6034 1185 : .store_into(psOptions->bVShift)
6035 1185 : .help(_("Force the use of vertical shift."));
6036 1185 : group.add_argument("-novshift", "-novshiftgrid")
6037 1185 : .flag()
6038 1185 : .store_into(psOptions->bNoVShift)
6039 1185 : .help(_("Disable the use of vertical shift."));
6040 : }
6041 :
6042 : argParser->add_input_format_argument(
6043 : psOptionsForBinary ? &psOptionsForBinary->aosAllowedInputDrivers
6044 1185 : : nullptr);
6045 :
6046 1185 : argParser->add_argument("-b", "-srcband")
6047 2370 : .metavar("<band>")
6048 1185 : .append()
6049 1185 : .store_into(psOptions->anSrcBands)
6050 1185 : .help(_("Specify input band(s) number to warp."));
6051 :
6052 1185 : argParser->add_argument("-dstband")
6053 2370 : .metavar("<band>")
6054 1185 : .append()
6055 1185 : .store_into(psOptions->anDstBands)
6056 1185 : .help(_("Specify the output band number in which to warp."));
6057 :
6058 : // Undocumented option used by gdal vector * algorithms
6059 1185 : argParser->add_argument("--invoked-from-gdal-algorithm")
6060 1185 : .store_into(psOptions->bInvokedFromGdalAlgorithm)
6061 1185 : .hidden();
6062 :
6063 1185 : if (psOptionsForBinary)
6064 : {
6065 92 : argParser->add_argument("src_dataset_name")
6066 184 : .metavar("<src_dataset_name>")
6067 92 : .nargs(argparse::nargs_pattern::at_least_one)
6068 93 : .action([psOptionsForBinary](const std::string &s)
6069 185 : { psOptionsForBinary->aosSrcFiles.AddString(s.c_str()); })
6070 92 : .help(_("Input dataset(s)."));
6071 :
6072 92 : argParser->add_argument("dst_dataset_name")
6073 184 : .metavar("<dst_dataset_name>")
6074 92 : .store_into(psOptionsForBinary->osDstFilename)
6075 92 : .help(_("Output dataset."));
6076 : }
6077 :
6078 2370 : return argParser;
6079 : }
6080 :
6081 : /************************************************************************/
6082 : /* GDALWarpAppGetParserUsage() */
6083 : /************************************************************************/
6084 :
6085 2 : std::string GDALWarpAppGetParserUsage()
6086 : {
6087 : try
6088 : {
6089 4 : GDALWarpAppOptions sOptions;
6090 4 : GDALWarpAppOptionsForBinary sOptionsForBinary;
6091 : auto argParser =
6092 4 : GDALWarpAppOptionsGetParser(&sOptions, &sOptionsForBinary);
6093 2 : return argParser->usage();
6094 : }
6095 0 : catch (const std::exception &err)
6096 : {
6097 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
6098 0 : err.what());
6099 0 : return std::string();
6100 : }
6101 : }
6102 :
6103 : /************************************************************************/
6104 : /* GDALWarpAppOptionsNew() */
6105 : /************************************************************************/
6106 :
6107 : #ifndef CheckHasEnoughAdditionalArgs_defined
6108 : #define CheckHasEnoughAdditionalArgs_defined
6109 :
6110 120 : static bool CheckHasEnoughAdditionalArgs(CSLConstList papszArgv, int i,
6111 : int nExtraArg, int nArgc)
6112 : {
6113 120 : if (i + nExtraArg >= nArgc)
6114 : {
6115 0 : CPLError(CE_Failure, CPLE_IllegalArg,
6116 0 : "%s option requires %d argument%s", papszArgv[i], nExtraArg,
6117 : nExtraArg == 1 ? "" : "s");
6118 0 : return false;
6119 : }
6120 120 : return true;
6121 : }
6122 : #endif
6123 :
6124 : #define CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(nExtraArg) \
6125 : if (!CheckHasEnoughAdditionalArgs(papszArgv, i, nExtraArg, nArgc)) \
6126 : { \
6127 : return nullptr; \
6128 : }
6129 :
6130 : /**
6131 : * Allocates a GDALWarpAppOptions struct.
6132 : *
6133 : * @param papszArgv NULL terminated list of options (potentially including
6134 : * filename and open options too), or NULL. The accepted options are the ones of
6135 : * the <a href="/programs/gdalwarp.html">gdalwarp</a> utility.
6136 : * @param psOptionsForBinary (output) may be NULL (and should generally be
6137 : * NULL), otherwise (gdal_translate_bin.cpp use case) must be allocated with
6138 : * GDALWarpAppOptionsForBinaryNew() prior to this
6139 : * function. Will be filled with potentially present filename, open options,...
6140 : * @return pointer to the allocated GDALWarpAppOptions struct. Must be freed
6141 : * with GDALWarpAppOptionsFree().
6142 : *
6143 : * @since GDAL 2.1
6144 : */
6145 :
6146 : GDALWarpAppOptions *
6147 1183 : GDALWarpAppOptionsNew(char **papszArgv,
6148 : GDALWarpAppOptionsForBinary *psOptionsForBinary)
6149 : {
6150 2366 : auto psOptions = std::make_unique<GDALWarpAppOptions>();
6151 :
6152 1183 : psOptions->aosArgs.Assign(CSLDuplicate(papszArgv), true);
6153 :
6154 : /* -------------------------------------------------------------------- */
6155 : /* Pre-processing for custom syntax that ArgumentParser does not */
6156 : /* support. */
6157 : /* -------------------------------------------------------------------- */
6158 :
6159 2366 : CPLStringList aosArgv;
6160 1183 : const int nArgc = CSLCount(papszArgv);
6161 8138 : for (int i = 0;
6162 8138 : i < nArgc && papszArgv != nullptr && papszArgv[i] != nullptr; i++)
6163 : {
6164 6955 : if (EQUAL(papszArgv[i], "-refine_gcps"))
6165 : {
6166 0 : CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(1);
6167 0 : psOptions->aosTransformerOptions.SetNameValue("REFINE_TOLERANCE",
6168 0 : papszArgv[++i]);
6169 0 : if (CPLAtof(papszArgv[i]) < 0)
6170 : {
6171 0 : CPLError(CE_Failure, CPLE_IllegalArg,
6172 : "The tolerance for -refine_gcps may not be negative.");
6173 0 : return nullptr;
6174 : }
6175 0 : if (i < nArgc - 1 && atoi(papszArgv[i + 1]) >= 0 &&
6176 0 : isdigit(static_cast<unsigned char>(papszArgv[i + 1][0])))
6177 : {
6178 0 : psOptions->aosTransformerOptions.SetNameValue(
6179 0 : "REFINE_MINIMUM_GCPS", papszArgv[++i]);
6180 : }
6181 : else
6182 : {
6183 0 : psOptions->aosTransformerOptions.SetNameValue(
6184 0 : "REFINE_MINIMUM_GCPS", "-1");
6185 : }
6186 : }
6187 6955 : else if (EQUAL(papszArgv[i], "-tr") && i + 1 < nArgc &&
6188 121 : EQUAL(papszArgv[i + 1], "square"))
6189 : {
6190 1 : ++i;
6191 1 : psOptions->bSquarePixels = true;
6192 1 : psOptions->bCreateOutput = true;
6193 : }
6194 6954 : else if (EQUAL(papszArgv[i], "-tr"))
6195 : {
6196 120 : CHECK_HAS_ENOUGH_ADDITIONAL_ARGS(2);
6197 120 : psOptions->dfXRes = CPLAtofM(papszArgv[++i]);
6198 120 : psOptions->dfYRes = fabs(CPLAtofM(papszArgv[++i]));
6199 120 : if (psOptions->dfXRes == 0 || psOptions->dfYRes == 0)
6200 : {
6201 0 : CPLError(CE_Failure, CPLE_IllegalArg,
6202 : "Wrong value for -tr parameters.");
6203 0 : return nullptr;
6204 : }
6205 120 : psOptions->bCreateOutput = true;
6206 : }
6207 : // argparser will be confused if the value of a string argument
6208 : // starts with a negative sign.
6209 6834 : else if (EQUAL(papszArgv[i], "-srcnodata") && i + 1 < nArgc)
6210 : {
6211 48 : ++i;
6212 48 : psOptions->osSrcNodata = papszArgv[i];
6213 : }
6214 : // argparser will be confused if the value of a string argument
6215 : // starts with a negative sign.
6216 6786 : else if (EQUAL(papszArgv[i], "-dstnodata") && i + 1 < nArgc)
6217 : {
6218 140 : ++i;
6219 140 : psOptions->osDstNodata = papszArgv[i];
6220 : }
6221 : else
6222 : {
6223 6646 : aosArgv.AddString(papszArgv[i]);
6224 : }
6225 : }
6226 :
6227 : try
6228 : {
6229 : auto argParser =
6230 2366 : GDALWarpAppOptionsGetParser(psOptions.get(), psOptionsForBinary);
6231 :
6232 1183 : argParser->parse_args_without_binary_name(aosArgv.List());
6233 :
6234 1353 : if (auto oTS = argParser->present<std::vector<int>>("-ts"))
6235 : {
6236 174 : psOptions->nForcePixels = (*oTS)[0];
6237 174 : psOptions->nForceLines = (*oTS)[1];
6238 174 : psOptions->bCreateOutput = true;
6239 : }
6240 :
6241 1370 : if (auto oTE = argParser->present<std::vector<double>>("-te"))
6242 : {
6243 191 : psOptions->dfMinX = (*oTE)[0];
6244 191 : psOptions->dfMinY = (*oTE)[1];
6245 191 : psOptions->dfMaxX = (*oTE)[2];
6246 191 : psOptions->dfMaxY = (*oTE)[3];
6247 191 : psOptions->aosTransformerOptions.SetNameValue(
6248 : "TARGET_EXTENT",
6249 191 : CPLSPrintf("%.17g,%.17g,%.17g,%.17g", psOptions->dfMinX,
6250 382 : psOptions->dfMinY, psOptions->dfMaxX,
6251 191 : psOptions->dfMaxY));
6252 191 : psOptions->bCreateOutput = true;
6253 : }
6254 :
6255 1184 : if (!psOptions->anDstBands.empty() &&
6256 5 : psOptions->anSrcBands.size() != psOptions->anDstBands.size())
6257 : {
6258 1 : CPLError(
6259 : CE_Failure, CPLE_IllegalArg,
6260 : "-srcband should be specified as many times as -dstband is");
6261 1 : return nullptr;
6262 : }
6263 1205 : else if (!psOptions->anSrcBands.empty() &&
6264 27 : psOptions->anDstBands.empty())
6265 : {
6266 60 : for (int i = 0; i < static_cast<int>(psOptions->anSrcBands.size());
6267 : ++i)
6268 : {
6269 37 : psOptions->anDstBands.push_back(i + 1);
6270 : }
6271 : }
6272 :
6273 1691 : if (!psOptions->osFormat.empty() ||
6274 513 : psOptions->eOutputType != GDT_Unknown)
6275 : {
6276 671 : psOptions->bCreateOutput = true;
6277 : }
6278 :
6279 1178 : if (psOptionsForBinary)
6280 88 : psOptionsForBinary->bCreateOutput = psOptions->bCreateOutput;
6281 :
6282 1178 : return psOptions.release();
6283 : }
6284 4 : catch (const std::exception &err)
6285 : {
6286 4 : CPLError(CE_Failure, CPLE_AppDefined, "%s", err.what());
6287 4 : return nullptr;
6288 : }
6289 : }
6290 :
6291 : /************************************************************************/
6292 : /* GDALWarpAppOptionsFree() */
6293 : /************************************************************************/
6294 :
6295 : /**
6296 : * Frees the GDALWarpAppOptions struct.
6297 : *
6298 : * @param psOptions the options struct for GDALWarp().
6299 : *
6300 : * @since GDAL 2.1
6301 : */
6302 :
6303 1178 : void GDALWarpAppOptionsFree(GDALWarpAppOptions *psOptions)
6304 : {
6305 1178 : delete psOptions;
6306 1178 : }
6307 :
6308 : /************************************************************************/
6309 : /* GDALWarpAppOptionsSetProgress() */
6310 : /************************************************************************/
6311 :
6312 : /**
6313 : * Set a progress function.
6314 : *
6315 : * @param psOptions the options struct for GDALWarp().
6316 : * @param pfnProgress the progress callback.
6317 : * @param pProgressData the user data for the progress callback.
6318 : *
6319 : * @since GDAL 2.1
6320 : */
6321 :
6322 136 : void GDALWarpAppOptionsSetProgress(GDALWarpAppOptions *psOptions,
6323 : GDALProgressFunc pfnProgress,
6324 : void *pProgressData)
6325 : {
6326 136 : psOptions->pfnProgress = pfnProgress ? pfnProgress : GDALDummyProgress;
6327 136 : psOptions->pProgressData = pProgressData;
6328 136 : if (pfnProgress == GDALTermProgress)
6329 0 : psOptions->bQuiet = false;
6330 136 : }
6331 :
6332 : /************************************************************************/
6333 : /* GDALWarpAppOptionsSetQuiet() */
6334 : /************************************************************************/
6335 :
6336 : /**
6337 : * Set a progress function.
6338 : *
6339 : * @param psOptions the options struct for GDALWarp().
6340 : * @param bQuiet whether GDALWarp() should emit messages on stdout.
6341 : *
6342 : * @since GDAL 2.3
6343 : */
6344 :
6345 81 : void GDALWarpAppOptionsSetQuiet(GDALWarpAppOptions *psOptions, int bQuiet)
6346 : {
6347 81 : psOptions->bQuiet = CPL_TO_BOOL(bQuiet);
6348 81 : }
6349 :
6350 : /************************************************************************/
6351 : /* GDALWarpAppOptionsSetWarpOption() */
6352 : /************************************************************************/
6353 :
6354 : /**
6355 : * Set a warp option
6356 : *
6357 : * @param psOptions the options struct for GDALWarp().
6358 : * @param pszKey key.
6359 : * @param pszValue value.
6360 : *
6361 : * @since GDAL 2.1
6362 : */
6363 :
6364 0 : void GDALWarpAppOptionsSetWarpOption(GDALWarpAppOptions *psOptions,
6365 : const char *pszKey, const char *pszValue)
6366 : {
6367 0 : psOptions->aosWarpOptions.SetNameValue(pszKey, pszValue);
6368 0 : }
6369 :
6370 : #undef CHECK_HAS_ENOUGH_ADDITIONAL_ARGS
|